Skip to content

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

Merged
thecodedrift merged 6 commits into
mainfrom
openspec/export-knowledge-prompts-2-export
Aug 10, 2026
Merged

feat(cli): export the knowledge prompts as @taskless/cli/prompts#87
thecodedrift merged 6 commits into
mainfrom
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

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 help command.

commands/help.ts owned the import.meta.glob over help/*.txt, the canonical/anonymous map build, the TOPIC_INPUT_SCHEMAS table, and renderRecipe. All of it now lives in src/prompts/recipes.ts, which help and onboard consume via getRecipe. The module carries no CLI runtime: embedded text, sprintf-js, applyCliInvocation, and the two leaf Zod input schemas.

Parity evidence. help output is byte-identical. It was captured across all 18 canonical topics plus their --anonymous variants, 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-changeset label.

Where the export lives

The public surface is unit 2, #87: the ./prompts subpath export, the Vite entry, the TOPICS/PromptOptions API, and the completeness check. That PR carries the changeset and archives the change.

stack: openspec-archived skips 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

PR Change Prerequisites
#70 (this PR) knowledge prompts: shared module (unit 1) none
#87 knowledge prompts: @taskless/cli/prompts export (unit 2) #70 (stacked on it)
#72 Vale binary packages none
#73 partition rules by engine none
#71 Vale engine + engine-selection topic #73 (executes the 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 main in 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

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
@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)

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

@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

@thecodedrift
thecodedrift force-pushed the openspec/export-knowledge-prompts-2-export branch from 156f01d to bcc236f Compare August 10, 2026 06:32
Base automatically changed from openspec/export-knowledge-prompts to main August 10, 2026 19:24
thecodedrift and others added 6 commits August 10, 2026 12:24
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>
@thecodedrift
thecodedrift force-pushed the openspec/export-knowledge-prompts-2-export branch from bcc236f to a5c2334 Compare August 10, 2026 19:24
@thecodedrift
thecodedrift merged commit bf1d8af into main Aug 10, 2026
4 checks passed
@thecodedrift
thecodedrift deleted the openspec/export-knowledge-prompts-2-export branch August 10, 2026 19:29
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