Skip to content

feat(cli): export the knowledge prompts as @taskless/cli/prompts - #87

Open
thecodedrift wants to merge 9 commits into
openspec/export-knowledge-promptsfrom
openspec/export-knowledge-prompts-2-export
Open

feat(cli): export the knowledge prompts as @taskless/cli/prompts#87
thecodedrift wants to merge 9 commits into
openspec/export-knowledge-promptsfrom
openspec/export-knowledge-prompts-2-export

Conversation

@thecodedrift

@thecodedrift thecodedrift commented Aug 6, 2026

Copy link
Copy Markdown
Member

Stack (root → tip):

Adds the @taskless/cli/prompts subpath 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)s resolves inside the package (CLI_VERSION from the build version, INPUT_SCHEMA from the Zod source, PACKAGE_MANAGER_DLX as an agent-fill marker), so a consumer never handles a template dialect.

PromptOptions carries anonymous, packageManagerDlx, and header. header: false drops 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

TOPICS ships static and nothing else, 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, 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 TOPICS when a consumer asks for it.

This also corrects three stale route examples in the spec delta that contradicted D6, before the delta was promoted into openspec/specs/.

Two implementation notes worth a reviewer's attention

Declarations come from tsc, not vite-plugin-dts. They are emitted by tsc --emitDeclarationOnly against a scoped tsconfig.prompts.json whose include is just the prompts entry plus the ambient build defines. 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 silently hand consumers a typed CLI surface the package has never promised. The "." export block is untouched, and a test asserts dist/index.d.ts is absent.

The declaration lands at dist/prompts/index.d.ts, not the literal dist/prompts.d.ts that task 2.2 names. tsc mirrors rootDir, so the layout follows src/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.ts imports ./recipes.js with an explicit extension, the only such import in the package. tsc copies the specifier verbatim into the published .d.ts, and the extensionless form fails for consumers on moduleResolution: node16 with TS2834.

Build

The Vite shebang() plugin is now scoped to the index entry. It previously prepended #!/usr/bin/env node to every entry chunk and set the executable bit, which would have made the importable module an executable script. dist/index.js keeps its shebang and mode 755; dist/prompts.js has neither.

Verification

  • pnpm --filter @taskless/cli typecheck, pnpm lint, pnpm --filter @taskless/cli test all clean; 442 tests across 38 files, including 23 new prompt tests.
  • vite build emits dist/index.js (with shebang, 755) and dist/prompts.js (no shebang, 644), plus dist/prompts/index.d.ts. No dist/index.d.ts.

Fixes OSS-20

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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --emitDeclarationOnly pipeline for the prompts entry and wire it into @taskless/cli’s build, exporting the new subpath with explicit types + import paths.
  • 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 (and package.json export) uses dist/prompts/index.d.ts (plus any referenced .d.ts alongside it). Updating the task text avoids confusion when auditing the archive later.
    openspec/specs/cli-knowledge-prompts/spec.md:58
  • Similarly, this scenario implies ci is 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

Comment thread openspec/specs/cli-knowledge-prompts/spec.md Outdated
Comment thread openspec/specs/cli-knowledge-prompts/spec.md Outdated
…e-prompts-2-export

* openspec/export-knowledge-prompts:
  chore: grow the changeset with the committed sg config
  docs(openspec): make cli-check internally consistent; correct the generateSgConfig note
  test(cli): isolate the missing-binary test from the host environment
  docs(openspec): archive partition-rules-by-engine
  ref(cli): declare COMMITTED_SG_CONFIG beside the layout it derives from
  docs(cli): describe the engine-partitioned layout in help and scaffolding
  feat(cli): run ast-grep against the committed sg config
  chore: grow the changeset with the runtime discovery path
  docs(openspec): correct the group-1 note, which no longer describes reality
  ref(cli): retire layout.ts now that ENGINE_LAYOUTS supersedes it
  feat(cli): discover runtime rules under the runtime engine directory
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 15m 46s —— View job


Claude finished the review

  • Gather context (diff, CLAUDE.md conventions)
  • Run multi-angle review (correctness, cleanup, altitude, conventions)
  • Verify candidate findings
  • Post inline comments
  • Post final summary

Overall assessment

This is a clean, well-scoped addition. The core design holds up under scrutiny: src/prompts/index.ts and recipes.ts genuinely carry no CLI runtime (verified the import graph against the test's own allowlist and the source files — everything checks out), the TOPICS/INTERNAL_TOPICS completeness pair is exhaustive against the 18 canonical help/*.txt files on disk today, the .js-extension workaround on the one import is correctly reasoned, and the shebang-plugin scoping change correctly narrows an invariant that used to apply to "every entry chunk" down to just the bin entry without breaking the single-entry case that existed before. Test coverage is thorough (placeholder resolution, header suppression, help-command parity, artifact/declaration presence, import-graph constraints).

I did find six issues worth a look, filed as inline comments:

  1. packages/cli/package.json:13-14 (most concrete) — build:dev/build:self only run vite build, never the new tsc -p tsconfig.prompts.json step. Both still emit a working prompts.js for their target dirs, but neither ever gets a matching prompts/index.d.ts — exactly the scenario vite.config.ts's own comments say those targets exist for (validating/dogfooding the build from another repo). Related: tsconfig.prompts.json's declarationDir is hardcoded to "dist" rather than following TASKLESS_BUILD_TARGET the way resolveOutDir() does, so fixing the above naively would misplace declarations relative to the JS.
  2. openspec/specs/cli-knowledge-prompts/spec.md:5 — the promoted spec's Purpose is still the archive tool's "TBD" placeholder, even though this PR is the one doing the archiving.
  3. openspec/specs/cli-knowledge-prompts/spec.md:50-58 — two scenarios describe calling the rule-create/rule-improve/ci prompts as if they're part of the public surface, but they're in INTERNAL_TOPICS, not TOPICS. (Copilot's review flagged the same thing, including a suppressed comment on the ci scenario — confirmed independently, still unaddressed.)
  4. packages/cli/vite.config.ts:143-150isBinEntry's parameter is a hand-rolled, all-optional structural type instead of Rollup's own OutputChunk/OutputAsset union, which weakens what the compiler can catch here. Low risk today since both call sites already guard on chunk.type === "chunk".
  5. packages/cli/test/prompts.test.ts:1-6 — the node:* builtin imports and the external vitest import aren't separated by a blank line, per .conventions/STYLEGUIDE-CODE.md's import-grouping rule.
  6. packages/cli/tsconfig.prompts.json:18 — worth a quick check of the actual build output: tsc's declaration emit isn't limited to include's root files, so it likely also emits dist/prompts/recipes.d.ts, dist/util/invocation.d.ts, and the two schema .d.ts files beyond just prompts/index.d.ts. Not a functional break (the exports map still only exposes prompts/index.d.ts, and dist/index.d.ts — the file actually being guarded against — stays absent), but more surface than the tsconfig's comment implies ships in the tarball.

None of these are blocking in the sense of breaking the feature as shipped for the prod/default build path, which is what CI exercises. #1 is the one I'd actually want addressed or explicitly deferred before merge, since it's a real gap in a build target the PR's own comments describe as intentionally supported.

Per instructions I did not run pnpm typecheck/lint/test/vite build myself — the PR body already reports these as clean, and CI reports check status on the PR itself.
(branch)

thecodedrift and others added 5 commits August 6, 2026 17:34
…-knowledge-prompts-2-export

* openspec/export-knowledge-prompts:
  fix(cli): size the vitest timeout to what the suite actually does
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>
…-knowledge-prompts-2-export

* openspec/export-knowledge-prompts:
  docs(openspec): point the change docs at the module layout that shipped
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>
Comment thread packages/cli/package.json
Comment thread openspec/specs/cli-knowledge-prompts/spec.md
Comment thread openspec/specs/cli-knowledge-prompts/spec.md
Comment thread packages/cli/vite.config.ts Outdated
Comment thread packages/cli/test/prompts.test.ts
Comment thread packages/cli/tsconfig.prompts.json
…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>
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude[bot] — "Claude finished @thecodedrift's task in 15m 46s —— Claude finished the review…"
#87 (comment)

All six findings were evaluated; each inline thread has a reply and is resolved. Fixed: the promoted spec's TBD Purpose and its internal-topic scenarios (0408475), the hand-rolled isBinEntry shape now typed against Rollup's bundle union as a type predicate, and the tsconfig.prompts.json comment corrected to say the include list bounds which entry is rooted rather than how many files are emitted (f8e1ce4). Same commit adds the missing assertion that dist/index.d.ts stays absent, which was the invariant the scoping exists to protect and the only one with no test.

Two were not taken as written. The build:dev/build:self declaration gap does not reach a consumer, because the ./prompts export resolves to ./dist/prompts.js unconditionally and nothing resolves a prompts entry out of dist-dev/dist-self; the declarationDir trap you flagged alongside it is now documented. The import-grouping nit is declined for consistency: every other test file in the package puts vitest directly after the node:* block, so fixing this one would make it the exception.

— AI Coding Agent

…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>
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @copilot-pull-request-reviewer — "## Pull request overview … 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…"
#87 (review)

Fixed in 156f01d: task 2.2 now says dist/prompts/index.d.ts from the scoped tsc --emitDeclarationOnly pass, matching the exports map and the actual build output. Task 2.3 (after vite build) and the matching design.md risk bullet carried the same imprecision and were corrected too; spec.md is untouched.

— AI Coding Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants