feat(cli): export the knowledge prompts as @taskless/cli/prompts - #87
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new public, typed @taskless/cli/prompts subpath export so non-CLI consumers (e.g. service-side generators) can import fully-rendered knowledge prompt text sourced from the same embedded help/*.txt recipes that power taskless help, without pulling in the CLI runtime.
Changes:
- Extend the CLI build to emit a second Vite library entry (
dist/prompts.js) while scoping the shebang/executable bit to the CLI binary entry only. - Add a dedicated
tsc --emitDeclarationOnlypipeline for the prompts entry and wire it into@taskless/cli’s build, exporting the new subpath with explicittypes+importpaths. - Add comprehensive tests covering placeholder resolution, header suppression, help parity, artifact/declaration presence, and “no CLI runtime” import-graph constraints; archive/promote the OpenSpec change artifacts/spec.
Reviewed changes
Copilot reviewed 9 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/vite.config.ts | Build emits index + prompts entries; shebang/chmod restricted to the CLI binary entry. |
| packages/cli/tsconfig.prompts.json | New scoped declaration-only tsconfig to generate .d.ts for the prompts subpath without producing dist/index.d.ts. |
| packages/cli/test/prompts.test.ts | New test suite validating rendering invariants, parity with help, build outputs, and import-graph constraints. |
| packages/cli/src/prompts/index.ts | New public prompts entrypoint exporting TOPICS, INTERNAL_TOPICS, PromptTopic, PromptOptions, getPrompt, and PROMPTS. |
| packages/cli/package.json | Adds ./prompts export (types + import) and runs tsc -p tsconfig.prompts.json as part of build. |
| openspec/specs/cli-knowledge-prompts/spec.md | Promoted spec for the feature; currently contains some stale/placeholder wording (see comments). |
| openspec/changes/archive/2026-08-06-export-knowledge-prompts/tasks.md | Archived tasks marked complete; one line still references an outdated declaration path. |
| openspec/changes/archive/2026-08-06-export-knowledge-prompts/specs/cli-knowledge-prompts/spec.md | Archived delta spec updated with static-based examples. |
| openspec/changes/archive/2026-08-06-export-knowledge-prompts/proposal.md | Archived proposal describing the feature and delivery shape; one module-layout description is now stale. |
| openspec/changes/archive/2026-08-06-export-knowledge-prompts/design.md | Archived design rationale; a couple references still point at the old module layout. |
| openspec/changes/archive/2026-08-06-export-knowledge-prompts/.openspec.yaml | OpenSpec archive metadata for the change. |
| .changeset/export-knowledge-prompts.md | Changeset documenting the new minor feature export. |
Suppressed comments (2)
openspec/changes/archive/2026-08-06-export-knowledge-prompts/tasks.md:13
- Task 2.2 still references a single-file
dist/prompts.d.ts, but the implementation (andpackage.jsonexport) usesdist/prompts/index.d.ts(plus any referenced.d.tsalongside it). Updating the task text avoids confusion when auditing the archive later.
openspec/specs/cli-knowledge-prompts/spec.md:58 - Similarly, this scenario implies
ciis an exported prompt topic, but it is currently listed under INTERNAL_TOPICS. Consider wording this in terms of rendering a recipe containing the placeholder, rather than calling an exported topic.
#### Scenario: Agent-fill marker defaults and overrides
- **WHEN** a consumer calls the `ci` prompt without options
- **THEN** `%(PACKAGE_MANAGER_DLX)s` renders as the default `<package-manager-dlx>` marker; supplying `packageManagerDlx` substitutes that value instead
|
@claude /review |
|
Claude finished @thecodedrift's task in 15m 46s —— View job Claude finished the review
Overall assessmentThis is a clean, well-scoped addition. The core design holds up under scrutiny: I did find six issues worth a look, filed as inline comments:
None of these are blocking in the sense of breaking the feature as shipped for the Per instructions I did not run |
All six findings were evaluated; each inline thread has a reply and is resolved. Fixed: the promoted spec's Two were not taken as written. The — AI Coding Agent |
Fixed in 156f01d: task 2.2 now says — AI Coding Agent |
156f01d to
bcc236f
Compare
Adds a `./prompts` subpath export so consumers can import the CLI's `help/*.txt` recipes instead of keeping a copy that drifts. Prompts are functions returning fully-rendered text: every `%(KEY)s` resolves inside the package, so no consumer handles a template dialect. `PromptOptions` carries `anonymous`, `packageManagerDlx`, and `header`. `header: false` exists because the header carries the CLI version, which would otherwise sit in an LLM consumer's prompt-cache key and be invalidated by every publish. `TOPICS` ships `static` alone, per design D6. The other 17 canonical topics are recorded in `INTERNAL_TOPICS`, and a completeness check fails when a recipe file is neither exported nor explicitly internal. Topic names are semver-tracked public API, so exporting one speculatively spends that promise for nothing. Two build details worth a look: Declarations come from `tsc --emitDeclarationOnly` against a scoped `tsconfig.prompts.json`, not `vite-plugin-dts`. A whole-`src` dts plugin would emit `dist/index.d.ts` as a side effect, and since the `"."` export has no `types` condition, TypeScript would fall back to that sibling file and hand consumers a typed CLI surface the package has never promised. The `"."` export block is untouched. `tsc` mirrors `rootDir`, so the declaration lands at `dist/prompts/index.d.ts` rather than the literal `dist/prompts.d.ts` task 2.2 names; the export map points at it directly. The Vite `shebang()` plugin is now scoped to the `index` entry. It prepended `#!/usr/bin/env node` to every entry chunk, which would have made the importable module an executable script. Also corrects three stale `route` examples in the spec delta that contradicted D6, and archives the change. Unit 2 of 2 for export-knowledge-prompts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prompts entry makes the build emit two library entries, so rollup hoists what `index` and `prompts` share into a sibling chunk that `dist/index.js` imports by relative path. The missing-binary test copied only the bin into its isolated directory, leaving that import dangling: the CLI died on ERR_MODULE_NOT_FOUND before it ever looked for ast-grep, and the assertion reported an empty stderr rather than the real cause. Copy the whole `dist/` instead. The isolation the test needs is the empty PATH and HOME, not a single-file bundle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts scenarios
The promoted spec still carried the archive placeholder for Purpose, which
reads to a later maintainer as an unfinished spec. State what the capability
is for and why one source and one renderer matter.
Two scenarios also described `rule-create`/`rule-improve`/`ci` as prompts a
consumer calls, but all three are INTERNAL_TOPICS and are not members of
`PromptTopic` — `getPrompt("ci")` does not type-check. Reword them around
the recipe carrying the placeholder, which is what the render path actually
guarantees, and name the topics as internal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Unit 1 unchecked tasks 1.3 and 1.4 because it does not implement them. This unit does, so the archived record checks them again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d CLI entry Three review findings on the build wiring: `isBinEntry` took a hand-rolled structural shape whose fields were all optional, so an `OutputAsset` satisfied it by having none of them. Take Rollup's own bundle union and return a type predicate instead, which also lets `generateBundle` drop its redundant `type === "chunk"` guard. `tsconfig.prompts.json`'s comment implied its include list bounds what gets emitted. It bounds which entry is rooted; emit follows the import graph, so `prompts/recipes`, `util/invocation`, and the two leaf schemas get declarations too. Say so, and record why `declarationDir` stays `dist` while `vite.config.ts` derives its output dir from TASKLESS_BUILD_TARGET: the `./prompts` export resolves to `./dist/prompts.js` unconditionally. The absence of `dist/index.d.ts` was the invariant that scoping exists to protect and the only one with no test behind it. Assert it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hange Task 2.2 said the build emits `dist/prompts.d.ts`. Declarations come from `tsc --emitDeclarationOnly` against `tsconfig.prompts.json`, and `tsc` mirrors `rootDir` structure, so `src/prompts/index.ts` emits to `dist/prompts/index.d.ts`, which is the path the `exports` map already points at. Task 2.3 and the matching design risk carried the same imprecision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bcc236f to
a5c2334
Compare
Stack (root → tip):
Adds the
@taskless/cli/promptssubpath export, so the generator and other service-side consumers can source authoring guidance from the same text the CLI serves rather than a local copy that drifts.Unit 2 of 2. Stacked on #70, which moved the recipe glob and the renderer into
src/prompts/recipes.ts. This PR adds the public surface on top of it and archives the change.The export
Prompts are functions returning fully-rendered text. Every
%(KEY)sresolves inside the package (CLI_VERSIONfrom the build version,INPUT_SCHEMAfrom the Zod source,PACKAGE_MANAGER_DLXas an agent-fill marker), so a consumer never handles a template dialect.PromptOptionscarriesanonymous,packageManagerDlx, andheader.header: falsedrops the version-bearing first line. That option exists for a specific reason: the header carries the CLI version, so a consumer placing the text in an LLM system prompt would otherwise have the version sitting in its prompt-cache key, invalidated by every CLI publish.Topic membership
TOPICSshipsstaticand nothing else, per design D6. The other 17 canonical topics are recorded inINTERNAL_TOPICS, and a completeness check fails when a recipe file is neither exported nor explicitly internal, so a new or deleted recipe cannot silently change the published surface.Topic names are semver-tracked public API. Exporting one speculatively spends that promise for nothing, so a topic joins
TOPICSwhen a consumer asks for it.This also corrects three stale
routeexamples in the spec delta that contradicted D6, before the delta was promoted intoopenspec/specs/.Two implementation notes worth a reviewer's attention
Declarations come from
tsc, notvite-plugin-dts. They are emitted bytsc --emitDeclarationOnlyagainst a scopedtsconfig.prompts.jsonwhoseincludeis just the prompts entry plus the ambient build defines. A whole-srcdts plugin would emitdist/index.d.tsas a side effect, and since the"."export has notypescondition, TypeScript would fall back to that sibling file and silently hand consumers a typed CLI surface the package has never promised. The"."export block is untouched, and a test assertsdist/index.d.tsis absent.The declaration lands at
dist/prompts/index.d.ts, not the literaldist/prompts.d.tsthat task 2.2 names.tscmirrorsrootDir, so the layout followssrc/prompts/index.ts. The export map points at it explicitly. Flagging this as a deliberate deviation from the written task rather than an oversight.src/prompts/index.tsimports./recipes.jswith an explicit extension, the only such import in the package.tsccopies the specifier verbatim into the published.d.ts, and the extensionless form fails for consumers onmoduleResolution: node16withTS2834.Build
The Vite
shebang()plugin is now scoped to theindexentry. It previously prepended#!/usr/bin/env nodeto every entry chunk and set the executable bit, which would have made the importable module an executable script.dist/index.jskeeps its shebang and mode755;dist/prompts.jshas neither.Verification
pnpm --filter @taskless/cli typecheck,pnpm lint,pnpm --filter @taskless/cli testall clean; 442 tests across 38 files, including 23 new prompt tests.vite buildemitsdist/index.js(with shebang,755) anddist/prompts.js(no shebang,644), plusdist/prompts/index.d.ts. Nodist/index.d.ts.Fixes OSS-20
Built on top of #70
Carries unit 1 of the knowledge-prompt export: a pure refactor that moves the recipe source and its renderer out from behind the
helpcommand.commands/help.tsowned theimport.meta.globoverhelp/*.txt, the canonical/anonymous map build, theTOPIC_INPUT_SCHEMAStable, andrenderRecipe. All of it now lives insrc/prompts/recipes.ts, whichhelpandonboardconsume viagetRecipe. The module carries no CLI runtime: embedded text,sprintf-js,applyCliInvocation, and the two leaf Zod input schemas.Parity evidence.
helpoutput is byte-identical. It was captured across all 18 canonical topics plus their--anonymousvariants, the topic index, and the unknown-topic error path, 38 captures in total, with a zero-byte diff before and after.Nothing observable changes, so this ships no release note and keeps the
skip-changesetlabel.Where the export lives
The public surface is unit 2, #87: the
./promptssubpath export, the Vite entry, theTOPICS/PromptOptionsAPI, and the completeness check. That PR carries the changeset and archives the change.stack: openspec-archivedskips this PR, because #87 targets this branch and so this is no longer the tip of its stack. The change is archived exactly once, on #87.Where this sits
@taskless/cli/promptsexport (unit 2)vale/directory it scaffolds) and #72 (needs a binary to resolve)This change is delivered as a two-PR stack, merging forward: unit 1 is independently safe in production because it changes no observable behavior, and unit 2 only adds a new export. Each lands on
mainin turn.#87 and #71 are coupled by exactly one line: whichever lands second adds the engine-selection topic to
TOPICS. Ordering between them doesn't matter.Downstream, the generator's decision router (TSKL-279) needs a published release containing #87 and #71. It consumes a normal release, with no prerelease and no path dependency, so it waits without blocking anything here.
Refs OSS-20