@popoverai/dotrequirements 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -23
- package/dist/cli.js +121 -61
- package/dist/codebase-to-spec/budget.d.ts +53 -0
- package/dist/codebase-to-spec/budget.js +80 -0
- package/dist/codebase-to-spec/cache.d.ts +49 -0
- package/dist/codebase-to-spec/cache.js +54 -0
- package/dist/codebase-to-spec/claude.d.ts +69 -0
- package/dist/codebase-to-spec/claude.js +126 -0
- package/dist/codebase-to-spec/compose.d.ts +49 -0
- package/dist/codebase-to-spec/compose.js +124 -0
- package/dist/codebase-to-spec/edit-loop.d.ts +54 -0
- package/dist/codebase-to-spec/edit-loop.js +195 -0
- package/dist/codebase-to-spec/editor.d.ts +54 -0
- package/dist/codebase-to-spec/editor.js +74 -0
- package/dist/codebase-to-spec/exit-codes.d.ts +40 -0
- package/dist/codebase-to-spec/exit-codes.js +58 -0
- package/dist/codebase-to-spec/fan-out.d.ts +63 -0
- package/dist/codebase-to-spec/fan-out.js +215 -0
- package/dist/codebase-to-spec/interactive.d.ts +30 -0
- package/dist/codebase-to-spec/interactive.js +48 -0
- package/dist/codebase-to-spec/outline-review-loop.d.ts +51 -0
- package/dist/codebase-to-spec/outline-review-loop.js +187 -0
- package/dist/codebase-to-spec/pack.d.ts +51 -0
- package/dist/codebase-to-spec/pack.js +127 -0
- package/dist/codebase-to-spec/planner.d.ts +41 -0
- package/dist/codebase-to-spec/planner.js +76 -0
- package/dist/codebase-to-spec/present.d.ts +94 -0
- package/dist/codebase-to-spec/present.js +288 -0
- package/dist/codebase-to-spec/progress.d.ts +33 -0
- package/dist/codebase-to-spec/progress.js +28 -0
- package/dist/codebase-to-spec/prompts/editor.d.ts +13 -0
- package/dist/codebase-to-spec/prompts/editor.js +57 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/outline-reviewer.js +87 -0
- package/dist/codebase-to-spec/prompts/planner-apply.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/planner-apply.js +32 -0
- package/dist/codebase-to-spec/prompts/planner-initial.d.ts +11 -0
- package/dist/codebase-to-spec/prompts/planner-initial.js +125 -0
- package/dist/codebase-to-spec/prompts/planner-revise.d.ts +14 -0
- package/dist/codebase-to-spec/prompts/planner-revise.js +60 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.d.ts +16 -0
- package/dist/codebase-to-spec/prompts/spec-reviewer.js +96 -0
- package/dist/codebase-to-spec/prompts/specifier.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/specifier.js +100 -0
- package/dist/codebase-to-spec/prompts/style-check.d.ts +12 -0
- package/dist/codebase-to-spec/prompts/style-check.js +78 -0
- package/dist/codebase-to-spec/schemas.d.ts +257 -0
- package/dist/codebase-to-spec/schemas.js +183 -0
- package/dist/codebase-to-spec/skill-install.d.ts +57 -0
- package/dist/codebase-to-spec/skill-install.js +79 -0
- package/dist/codebase-to-spec/slice.d.ts +49 -0
- package/dist/codebase-to-spec/slice.js +111 -0
- package/dist/codebase-to-spec/specifier.d.ts +60 -0
- package/dist/codebase-to-spec/specifier.js +79 -0
- package/dist/codebase-to-spec/style-check.d.ts +29 -0
- package/dist/codebase-to-spec/style-check.js +33 -0
- package/dist/codebase-to-spec/summary.d.ts +51 -0
- package/dist/codebase-to-spec/summary.js +183 -0
- package/dist/codebase-to-spec/validate.d.ts +46 -0
- package/dist/codebase-to-spec/validate.js +130 -0
- package/dist/commands/acceptance-test.d.ts +6 -0
- package/dist/commands/acceptance-test.js +212 -0
- package/dist/commands/ai-setup.d.ts +5 -0
- package/dist/commands/ai-setup.js +441 -0
- package/dist/commands/browsertest.d.ts +0 -1
- package/dist/commands/browsertest.js +51 -26
- package/dist/commands/codebase-to-spec/compose.d.ts +14 -0
- package/dist/commands/codebase-to-spec/compose.js +57 -0
- package/dist/commands/codebase-to-spec/edit-loop.d.ts +16 -0
- package/dist/commands/codebase-to-spec/edit-loop.js +83 -0
- package/dist/commands/codebase-to-spec/fan-out.d.ts +19 -0
- package/dist/commands/codebase-to-spec/fan-out.js +77 -0
- package/dist/commands/codebase-to-spec/index.d.ts +9 -0
- package/dist/commands/codebase-to-spec/index.js +135 -0
- package/dist/commands/codebase-to-spec/pack.d.ts +22 -0
- package/dist/commands/codebase-to-spec/pack.js +76 -0
- package/dist/commands/codebase-to-spec/plan-loop.d.ts +26 -0
- package/dist/commands/codebase-to-spec/plan-loop.js +105 -0
- package/dist/commands/codebase-to-spec/present.d.ts +21 -0
- package/dist/commands/codebase-to-spec/present.js +92 -0
- package/dist/commands/codebase-to-spec/run.d.ts +20 -0
- package/dist/commands/codebase-to-spec/run.js +85 -0
- package/dist/commands/codebase-to-spec/skill-install.d.ts +20 -0
- package/dist/commands/codebase-to-spec/skill-install.js +51 -0
- package/dist/commands/codebase-to-spec/specify-area.d.ts +18 -0
- package/dist/commands/codebase-to-spec/specify-area.js +82 -0
- package/dist/commands/codebase-to-spec/style-check.d.ts +15 -0
- package/dist/commands/codebase-to-spec/style-check.js +42 -0
- package/dist/commands/codebase-to-spec/validate.d.ts +18 -0
- package/dist/commands/codebase-to-spec/validate.js +38 -0
- package/dist/commands/create-requirement-document.d.ts +2 -0
- package/dist/commands/create-requirement-document.js +41 -0
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +55 -0
- package/dist/commands/init.js +132 -117
- package/dist/commands/link.js +27 -27
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/mcp-setup.js +159 -149
- package/dist/commands/mcp.js +1 -1
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/pull.js +116 -121
- package/dist/commands/push.js +106 -112
- package/dist/commands/report.d.ts +6 -2
- package/dist/commands/report.js +177 -122
- package/dist/commands/requirements-for.d.ts +2 -0
- package/dist/commands/requirements-for.js +29 -0
- package/dist/commands/review-test.d.ts +2 -0
- package/dist/commands/review-test.js +75 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.js +39 -0
- package/dist/commands/style-check.d.ts +7 -0
- package/dist/commands/style-check.js +75 -0
- package/dist/commands/test.js +53 -59
- package/dist/commands/tests-for.d.ts +2 -0
- package/dist/commands/tests-for.js +80 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +72 -0
- package/dist/config.js +1 -1
- package/dist/convex.d.ts +34 -22
- package/dist/convex.js +38 -22
- package/dist/harness/cache.d.ts +1 -5
- package/dist/harness/cache.js +49 -59
- package/dist/harness/convexReporting.d.ts +1 -1
- package/dist/harness/convexReporting.js +9 -7
- package/dist/harness/coverageCache.js +3 -3
- package/dist/harness/finalize.js +59 -46
- package/dist/harness/index.d.ts +6 -7
- package/dist/harness/index.js +9 -10
- package/dist/harness/prepare.js +6 -5
- package/dist/harness/requirementsLoader.d.ts +2 -2
- package/dist/harness/requirementsLoader.js +13 -35
- package/dist/harness/tracking.js +18 -18
- package/dist/harness/types.d.ts +1 -1
- package/dist/mcp/convexClient.d.ts +0 -39
- package/dist/mcp/convexClient.js +2 -107
- package/dist/mcp/grep.d.ts +1 -1
- package/dist/mcp/grep.js +87 -42
- package/dist/mcp/handlers/authoring.d.ts +1 -1
- package/dist/mcp/handlers/authoring.js +30 -234
- package/dist/mcp/handlers/coverage.d.ts +1 -1
- package/dist/mcp/handlers/coverage.js +13 -15
- package/dist/mcp/handlers/debug.d.ts +2 -3
- package/dist/mcp/handlers/debug.js +10 -10
- package/dist/mcp/handlers/get.d.ts +1 -1
- package/dist/mcp/handlers/get.js +11 -10
- package/dist/mcp/handlers/index.d.ts +20 -20
- package/dist/mcp/handlers/index.js +10 -10
- package/dist/mcp/handlers/list.d.ts +4 -33
- package/dist/mcp/handlers/list.js +16 -38
- package/dist/mcp/handlers/push.d.ts +1 -1
- package/dist/mcp/handlers/push.js +28 -18
- package/dist/mcp/handlers/report.d.ts +16 -0
- package/dist/mcp/handlers/report.js +134 -0
- package/dist/mcp/handlers/review.d.ts +1 -1
- package/dist/mcp/handlers/review.js +40 -59
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +7 -9
- package/dist/mcp/handlers/test-mapping.d.ts +1 -1
- package/dist/mcp/handlers/test-mapping.js +14 -14
- package/dist/mcp/handlers/types.d.ts +3 -3
- package/dist/mcp/handlers/types.js +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +156 -178
- package/dist/mcp/requirements.d.ts +2 -2
- package/dist/mcp/requirements.js +30 -30
- package/dist/mcp/testCodeExtractor.js +24 -26
- package/dist/mcp/types.d.ts +1 -1
- package/dist/push/core.d.ts +2 -2
- package/dist/push/core.js +20 -20
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +2 -2
- package/dist/requirements/cloud-ai.d.ts +57 -0
- package/dist/requirements/cloud-ai.js +104 -0
- package/dist/requirements/cloud-coverage.d.ts +41 -0
- package/dist/requirements/cloud-coverage.js +60 -0
- package/dist/requirements/coverage.d.ts +45 -0
- package/dist/requirements/coverage.js +114 -0
- package/dist/requirements/grep.d.ts +33 -0
- package/dist/requirements/grep.js +306 -0
- package/dist/requirements/index.d.ts +73 -0
- package/dist/requirements/index.js +174 -0
- package/dist/requirements/style-guide.d.ts +67 -0
- package/dist/requirements/style-guide.js +299 -0
- package/dist/requirements/testCodeExtractor.d.ts +22 -0
- package/dist/requirements/testCodeExtractor.js +150 -0
- package/dist/schema/browser.d.ts +8 -6
- package/dist/schema/browser.js +14 -14
- package/dist/schema/builder.d.ts +1 -1
- package/dist/schema/builder.js +13 -44
- package/dist/schema/conversions.d.ts +2 -2
- package/dist/schema/conversions.js +11 -11
- package/dist/schema/index.d.ts +9 -7
- package/dist/schema/index.js +15 -13
- package/dist/schema/parser-core.d.ts +1 -1
- package/dist/schema/parser-core.js +23 -22
- package/dist/schema/parser.d.ts +3 -3
- package/dist/schema/parser.js +27 -31
- package/dist/schema/resolver.d.ts +1 -1
- package/dist/schema/resolver.js +9 -9
- package/dist/schema/scenario.d.ts +91 -0
- package/dist/schema/scenario.js +82 -0
- package/dist/schema/schemas.d.ts +3 -3
- package/dist/schema/schemas.js +41 -28
- package/dist/schema/test-schema.js +27 -27
- package/dist/templates/context-file-section.md +3 -2
- package/dist/templates/example-requirements.js +1 -1
- package/dist/templates/example-requirements.ts +3 -1
- package/dist/templates/requirements-readme.js +1 -1
- package/dist/templates/requirements-readme.ts +1 -1
- package/dist/templates/skills/codebase-to-spec/SKILL.md +118 -0
- package/dist/utils/brand.js +3 -3
- package/dist/utils/browser-launch.js +4 -4
- package/dist/utils/context-file.d.ts +1 -1
- package/dist/utils/context-file.js +26 -26
- package/dist/utils/env.js +7 -7
- package/dist/utils/gitignore.js +7 -7
- package/dist/utils/oauth-callback-server.d.ts +1 -1
- package/dist/utils/oauth-callback-server.js +27 -25
- package/dist/utils/oauth-flow.js +32 -29
- package/dist/utils/project-discovery.d.ts +3 -3
- package/dist/utils/project-discovery.js +18 -17
- package/dist/utils/project-name.js +8 -8
- package/dist/utils/project-selector.d.ts +1 -1
- package/dist/utils/project-selector.js +24 -21
- package/dist/utils/project-settings.d.ts +5 -4
- package/dist/utils/project-settings.js +28 -20
- package/dist/utils/templates.js +6 -6
- package/package.json +2 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack stage: produce compressed and uncompressed views of a codebase.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Repomix's programmatic API to write:
|
|
5
|
+
* - `.dotrequirements-cache/overview.txt` — compressed (tree-sitter signatures)
|
|
6
|
+
* - `.dotrequirements-cache/source.txt` — uncompressed (full source for slices)
|
|
7
|
+
*
|
|
8
|
+
* Requirements covered:
|
|
9
|
+
* - CTS-PACK-1: Pack stage produces compressed and uncompressed views
|
|
10
|
+
* - CTS-PACK-2: Engineer can extend the ignore list per project
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
/**
|
|
15
|
+
* The curated default ignore list. These patterns are applied in addition to
|
|
16
|
+
* `.gitignore` and any project-level `.dotrequirements-ignore`.
|
|
17
|
+
*
|
|
18
|
+
* Sized to remove obvious non-behavioral noise: generated code, package
|
|
19
|
+
* manifests/lockfiles, vendored libraries, build artifacts, large binaries.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_IGNORES = [
|
|
22
|
+
// Vendored dependencies
|
|
23
|
+
"node_modules/**",
|
|
24
|
+
"vendor/**",
|
|
25
|
+
".pnp.*",
|
|
26
|
+
// Build outputs
|
|
27
|
+
"dist/**",
|
|
28
|
+
"build/**",
|
|
29
|
+
"out/**",
|
|
30
|
+
".next/**",
|
|
31
|
+
".nuxt/**",
|
|
32
|
+
".svelte-kit/**",
|
|
33
|
+
".turbo/**",
|
|
34
|
+
".parcel-cache/**",
|
|
35
|
+
// Generated code
|
|
36
|
+
"**/*.generated.*",
|
|
37
|
+
"**/_generated/**",
|
|
38
|
+
"**/__generated__/**",
|
|
39
|
+
"**/*.pb.ts",
|
|
40
|
+
"**/*.pb.go",
|
|
41
|
+
// Lockfiles
|
|
42
|
+
"package-lock.json",
|
|
43
|
+
"pnpm-lock.yaml",
|
|
44
|
+
"yarn.lock",
|
|
45
|
+
"bun.lockb",
|
|
46
|
+
"composer.lock",
|
|
47
|
+
"Gemfile.lock",
|
|
48
|
+
"poetry.lock",
|
|
49
|
+
"uv.lock",
|
|
50
|
+
"Cargo.lock",
|
|
51
|
+
// Coverage and test artifacts
|
|
52
|
+
"coverage/**",
|
|
53
|
+
".nyc_output/**",
|
|
54
|
+
".next-cache/**",
|
|
55
|
+
// IDE / editor
|
|
56
|
+
".idea/**",
|
|
57
|
+
".vscode/**",
|
|
58
|
+
// OS files
|
|
59
|
+
".DS_Store",
|
|
60
|
+
"Thumbs.db",
|
|
61
|
+
// Project caches we own
|
|
62
|
+
".dotrequirements-cache/**",
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Read project-level ignore patterns from `.dotrequirements-ignore` (and
|
|
66
|
+
* `.dotreqignore` as a shorthand), if either exists. Returns an empty array if
|
|
67
|
+
* no file is present.
|
|
68
|
+
*/
|
|
69
|
+
export function readProjectIgnores(projectRoot) {
|
|
70
|
+
const candidates = [".dotrequirements-ignore", ".dotreqignore"];
|
|
71
|
+
for (const name of candidates) {
|
|
72
|
+
const p = join(projectRoot, name);
|
|
73
|
+
if (existsSync(p)) {
|
|
74
|
+
const content = readFileSync(p, "utf-8");
|
|
75
|
+
return content
|
|
76
|
+
.split(/\r?\n/)
|
|
77
|
+
.map((line) => line.trim())
|
|
78
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run Repomix twice over the same scope: once compressed, once uncompressed.
|
|
85
|
+
*
|
|
86
|
+
* We use Repomix's programmatic API rather than `npx repomix` so the CLI does
|
|
87
|
+
* not require a separate install and so we can control config precisely.
|
|
88
|
+
*
|
|
89
|
+
* NOTE: Repomix's TypeScript API is invoked via `runCli`. We invoke it as the
|
|
90
|
+
* library exposes it; the surface is small enough that this wraps cleanly.
|
|
91
|
+
*/
|
|
92
|
+
export async function runPack(options) {
|
|
93
|
+
const { projectRoot, paths, scope, extraIgnores = [] } = options;
|
|
94
|
+
const ignores = [
|
|
95
|
+
...DEFAULT_IGNORES,
|
|
96
|
+
...readProjectIgnores(projectRoot),
|
|
97
|
+
...extraIgnores,
|
|
98
|
+
];
|
|
99
|
+
const target = scope ? join(projectRoot, scope) : projectRoot;
|
|
100
|
+
// Import dynamically — Repomix is heavy and we don't want to load it at CLI
|
|
101
|
+
// boot time for unrelated commands.
|
|
102
|
+
const repomix = await import("repomix");
|
|
103
|
+
const { runCli } = repomix;
|
|
104
|
+
// Run compressed (overview)
|
|
105
|
+
await runCli([target], projectRoot, {
|
|
106
|
+
output: paths.overview,
|
|
107
|
+
style: "plain",
|
|
108
|
+
compress: true,
|
|
109
|
+
ignore: ignores.join(","),
|
|
110
|
+
});
|
|
111
|
+
// Run uncompressed (source)
|
|
112
|
+
await runCli([target], projectRoot, {
|
|
113
|
+
output: paths.source,
|
|
114
|
+
style: "plain",
|
|
115
|
+
compress: false,
|
|
116
|
+
ignore: ignores.join(","),
|
|
117
|
+
});
|
|
118
|
+
// Count "File:" headers in the uncompressed pack to report file count
|
|
119
|
+
const sourceContent = readFileSync(paths.source, "utf-8");
|
|
120
|
+
const fileCount = (sourceContent.match(/^File: /gm) ?? []).length;
|
|
121
|
+
return {
|
|
122
|
+
overviewPath: paths.overview,
|
|
123
|
+
sourcePath: paths.source,
|
|
124
|
+
fileCount,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=pack.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planner functions (initial and revise).
|
|
3
|
+
*
|
|
4
|
+
* Two modes correspond to two places the planner is invoked:
|
|
5
|
+
* - initial: first outline from the compressed pack (CTS-PLAN-1)
|
|
6
|
+
* - revise: address reviewer feedback — works for both
|
|
7
|
+
* `requires-another-review` (CTS-PLAN-5) and
|
|
8
|
+
* `approved-with-revisions` (CTS-PLAN-4). The orchestration
|
|
9
|
+
* decides whether to loop back to the reviewer or proceed to
|
|
10
|
+
* fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Each mode is stateless — a fresh claude -p session per call.
|
|
13
|
+
*/
|
|
14
|
+
import { type ClaudeRunner } from "./claude.js";
|
|
15
|
+
import { type Outline, type OutlineReview } from "./schemas.js";
|
|
16
|
+
export interface PlannerContext {
|
|
17
|
+
/** Path to the compressed pack file (readable by the spawned agent). */
|
|
18
|
+
overviewPath: string;
|
|
19
|
+
/** Directories the agent should have read access to (e.g., the cache dir). */
|
|
20
|
+
addDirs: string[];
|
|
21
|
+
/** Optional claude runner override (for tests). */
|
|
22
|
+
runner?: ClaudeRunner;
|
|
23
|
+
/** Optional model override. */
|
|
24
|
+
model?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run the planner in initial mode and return a parsed outline.
|
|
28
|
+
* Requirement: CTS-PLAN-1
|
|
29
|
+
*/
|
|
30
|
+
export declare function runInitialPlanner(ctx: PlannerContext): Promise<Outline>;
|
|
31
|
+
/**
|
|
32
|
+
* Run the planner in revise mode — produce a new outline that addresses the
|
|
33
|
+
* reviewer's output. Handles both verdicts that trigger another planner pass:
|
|
34
|
+
* `requires-another-review` (categorized findings, judgment-based) and
|
|
35
|
+
* `approved-with-revisions` (explicit revisions list). The same prompt handles
|
|
36
|
+
* both shapes; the prompt switches behavior based on what's in the review.
|
|
37
|
+
*
|
|
38
|
+
* Requirements: CTS-PLAN-4, CTS-PLAN-5
|
|
39
|
+
*/
|
|
40
|
+
export declare function runRevisingPlanner(ctx: PlannerContext, priorOutline: Outline, review: OutlineReview): Promise<Outline>;
|
|
41
|
+
//# sourceMappingURL=planner.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planner functions (initial and revise).
|
|
3
|
+
*
|
|
4
|
+
* Two modes correspond to two places the planner is invoked:
|
|
5
|
+
* - initial: first outline from the compressed pack (CTS-PLAN-1)
|
|
6
|
+
* - revise: address reviewer feedback — works for both
|
|
7
|
+
* `requires-another-review` (CTS-PLAN-5) and
|
|
8
|
+
* `approved-with-revisions` (CTS-PLAN-4). The orchestration
|
|
9
|
+
* decides whether to loop back to the reviewer or proceed to
|
|
10
|
+
* fan-out.
|
|
11
|
+
*
|
|
12
|
+
* Each mode is stateless — a fresh claude -p session per call.
|
|
13
|
+
*/
|
|
14
|
+
import { runClaude } from "./claude.js";
|
|
15
|
+
import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
|
|
16
|
+
import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
|
|
17
|
+
import { OUTLINE_JSON_SCHEMA, parseOutline, } from "./schemas.js";
|
|
18
|
+
/**
|
|
19
|
+
* Run the planner in initial mode and return a parsed outline.
|
|
20
|
+
* Requirement: CTS-PLAN-1
|
|
21
|
+
*/
|
|
22
|
+
export async function runInitialPlanner(ctx) {
|
|
23
|
+
const runner = ctx.runner ?? runClaude;
|
|
24
|
+
const result = await runner({
|
|
25
|
+
systemPrompt: PLANNER_INITIAL_PROMPT,
|
|
26
|
+
userMessage: `The compressed packed codebase is at: ${ctx.overviewPath}\n\nRead it and produce the outline JSON described in your system prompt.`,
|
|
27
|
+
model: ctx.model,
|
|
28
|
+
tools: ["Read"],
|
|
29
|
+
addDirs: ctx.addDirs,
|
|
30
|
+
jsonSchema: OUTLINE_JSON_SCHEMA,
|
|
31
|
+
});
|
|
32
|
+
if (result.exitCode !== 0) {
|
|
33
|
+
throw new Error(`Planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
34
|
+
}
|
|
35
|
+
return parseOutline(result.stdout);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run the planner in revise mode — produce a new outline that addresses the
|
|
39
|
+
* reviewer's output. Handles both verdicts that trigger another planner pass:
|
|
40
|
+
* `requires-another-review` (categorized findings, judgment-based) and
|
|
41
|
+
* `approved-with-revisions` (explicit revisions list). The same prompt handles
|
|
42
|
+
* both shapes; the prompt switches behavior based on what's in the review.
|
|
43
|
+
*
|
|
44
|
+
* Requirements: CTS-PLAN-4, CTS-PLAN-5
|
|
45
|
+
*/
|
|
46
|
+
export async function runRevisingPlanner(ctx, priorOutline, review) {
|
|
47
|
+
const runner = ctx.runner ?? runClaude;
|
|
48
|
+
const userMessage = [
|
|
49
|
+
`The compressed packed codebase is at: ${ctx.overviewPath}`,
|
|
50
|
+
``,
|
|
51
|
+
`## Prior outline`,
|
|
52
|
+
`\`\`\`json`,
|
|
53
|
+
JSON.stringify(priorOutline, null, 2),
|
|
54
|
+
`\`\`\``,
|
|
55
|
+
``,
|
|
56
|
+
`## Reviewer output`,
|
|
57
|
+
`\`\`\`json`,
|
|
58
|
+
JSON.stringify(review, null, 2),
|
|
59
|
+
`\`\`\``,
|
|
60
|
+
``,
|
|
61
|
+
`Produce a revised outline JSON that addresses the reviewer's output. Output JSON only.`,
|
|
62
|
+
].join("\n");
|
|
63
|
+
const result = await runner({
|
|
64
|
+
systemPrompt: PLANNER_REVISE_PROMPT,
|
|
65
|
+
userMessage,
|
|
66
|
+
model: ctx.model,
|
|
67
|
+
tools: ["Read"],
|
|
68
|
+
addDirs: ctx.addDirs,
|
|
69
|
+
jsonSchema: OUTLINE_JSON_SCHEMA,
|
|
70
|
+
});
|
|
71
|
+
if (result.exitCode !== 0) {
|
|
72
|
+
throw new Error(`Revising planner failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);
|
|
73
|
+
}
|
|
74
|
+
return parseOutline(result.stdout);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Present stage: write the final spec to .requirements/.
|
|
3
|
+
*
|
|
4
|
+
* - When the outline has 5+ areas, split into per-area files
|
|
5
|
+
* (`.requirements/<sanitized-area-name>.requirements.md`).
|
|
6
|
+
* - When the outline has fewer than 5 areas, write a single document at
|
|
7
|
+
* `.requirements/<sanitized-title>.requirements.md`.
|
|
8
|
+
* - Overwrite handling depends on interactive vs non-interactive mode.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-PRESENT-1: Final spec written to .requirements/, split when 5+ areas
|
|
12
|
+
* - CTS-PRESENT-2: Interactive overwrite prompt with three choices
|
|
13
|
+
* - CTS-PRESENT-3: Non-interactive overwrites require --overwrite or --skip-existing
|
|
14
|
+
*/
|
|
15
|
+
import type { Outline } from "./schemas.js";
|
|
16
|
+
export declare const AREA_SPLIT_THRESHOLD = 5;
|
|
17
|
+
export type OverwritePolicy = "prompt" | "overwrite" | "skip-existing" | "fail-fast";
|
|
18
|
+
export interface PresentOptions {
|
|
19
|
+
outline: Outline;
|
|
20
|
+
finalSpecPath: string;
|
|
21
|
+
/** Project root (.requirements/ will be created under this). */
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
overwritePolicy: OverwritePolicy;
|
|
24
|
+
}
|
|
25
|
+
export interface PresentResult {
|
|
26
|
+
/** Paths written under .requirements/ (or those that would have been written, in fail cases). */
|
|
27
|
+
outputPaths: string[];
|
|
28
|
+
/** Per-file action taken. */
|
|
29
|
+
actions: PresentAction[];
|
|
30
|
+
/** True if every file landed successfully. */
|
|
31
|
+
allWritten: boolean;
|
|
32
|
+
/** Reason for failure, if `allWritten` is false in fail-fast mode. */
|
|
33
|
+
conflictPath?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PresentAction {
|
|
36
|
+
path: string;
|
|
37
|
+
action: "created" | "overwrote" | "skipped" | "merged" | "refused";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the output path(s) the present stage would write to.
|
|
41
|
+
*/
|
|
42
|
+
export declare function planPresentPaths(outline: Outline, projectRoot: string): {
|
|
43
|
+
mode: "single" | "split";
|
|
44
|
+
paths: {
|
|
45
|
+
area?: string;
|
|
46
|
+
outPath: string;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Split a composed spec into per-area sections.
|
|
51
|
+
*
|
|
52
|
+
* The compose stage writes:
|
|
53
|
+
*
|
|
54
|
+
* ---
|
|
55
|
+
* frontmatter
|
|
56
|
+
* ---
|
|
57
|
+
*
|
|
58
|
+
* # Title
|
|
59
|
+
*
|
|
60
|
+
* Summary.
|
|
61
|
+
*
|
|
62
|
+
* ---
|
|
63
|
+
*
|
|
64
|
+
* ## Area One
|
|
65
|
+
* ...
|
|
66
|
+
*
|
|
67
|
+
* ---
|
|
68
|
+
*
|
|
69
|
+
* ## Area Two
|
|
70
|
+
* ...
|
|
71
|
+
*
|
|
72
|
+
* We split on `\n---\n\n## ` and pair each chunk with its area heading.
|
|
73
|
+
*/
|
|
74
|
+
export declare function splitComposedSpec(composed: string, outline: Outline): Map<string, string>;
|
|
75
|
+
/**
|
|
76
|
+
* Build a single-file requirements document from the composed spec.
|
|
77
|
+
* The composed spec IS this document (with the right frontmatter and structure).
|
|
78
|
+
* We just read it and return it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildSingleFileDoc(composedSpec: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Build a per-area requirements document for split mode.
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildAreaDoc(outline: Outline, areaName: string, areaBody: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Run the present stage.
|
|
87
|
+
*
|
|
88
|
+
* Returns a list of actions taken per path. In fail-fast mode, if any path
|
|
89
|
+
* would conflict, the function returns early with `conflictPath` set and
|
|
90
|
+
* `allWritten = false` — no files are written in this case (preserving the
|
|
91
|
+
* "no file overwritten without choice" guarantee from CTS-PRESENT-2.1).
|
|
92
|
+
*/
|
|
93
|
+
export declare function runPresent(options: PresentOptions): Promise<PresentResult>;
|
|
94
|
+
//# sourceMappingURL=present.d.ts.map
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Present stage: write the final spec to .requirements/.
|
|
3
|
+
*
|
|
4
|
+
* - When the outline has 5+ areas, split into per-area files
|
|
5
|
+
* (`.requirements/<sanitized-area-name>.requirements.md`).
|
|
6
|
+
* - When the outline has fewer than 5 areas, write a single document at
|
|
7
|
+
* `.requirements/<sanitized-title>.requirements.md`.
|
|
8
|
+
* - Overwrite handling depends on interactive vs non-interactive mode.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-PRESENT-1: Final spec written to .requirements/, split when 5+ areas
|
|
12
|
+
* - CTS-PRESENT-2: Interactive overwrite prompt with three choices
|
|
13
|
+
* - CTS-PRESENT-3: Non-interactive overwrites require --overwrite or --skip-existing
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { parseRequirementsFile } from "../schema/parser.js";
|
|
18
|
+
import { sanitizeAreaName } from "./fan-out.js";
|
|
19
|
+
import { promptOverwriteChoice } from "./interactive.js";
|
|
20
|
+
export const AREA_SPLIT_THRESHOLD = 5;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve the output path(s) the present stage would write to.
|
|
23
|
+
*/
|
|
24
|
+
export function planPresentPaths(outline, projectRoot) {
|
|
25
|
+
const requirementsDir = join(projectRoot, ".requirements");
|
|
26
|
+
if (outline.areas.length >= AREA_SPLIT_THRESHOLD) {
|
|
27
|
+
return {
|
|
28
|
+
mode: "split",
|
|
29
|
+
paths: outline.areas.map((area) => ({
|
|
30
|
+
area: area.name,
|
|
31
|
+
outPath: join(requirementsDir, `${sanitizeAreaName(area.name)}.requirements.md`),
|
|
32
|
+
})),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
mode: "single",
|
|
37
|
+
paths: [
|
|
38
|
+
{
|
|
39
|
+
outPath: join(requirementsDir, `${sanitizeAreaName(outline.title)}.requirements.md`),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Split a composed spec into per-area sections.
|
|
46
|
+
*
|
|
47
|
+
* The compose stage writes:
|
|
48
|
+
*
|
|
49
|
+
* ---
|
|
50
|
+
* frontmatter
|
|
51
|
+
* ---
|
|
52
|
+
*
|
|
53
|
+
* # Title
|
|
54
|
+
*
|
|
55
|
+
* Summary.
|
|
56
|
+
*
|
|
57
|
+
* ---
|
|
58
|
+
*
|
|
59
|
+
* ## Area One
|
|
60
|
+
* ...
|
|
61
|
+
*
|
|
62
|
+
* ---
|
|
63
|
+
*
|
|
64
|
+
* ## Area Two
|
|
65
|
+
* ...
|
|
66
|
+
*
|
|
67
|
+
* We split on `\n---\n\n## ` and pair each chunk with its area heading.
|
|
68
|
+
*/
|
|
69
|
+
export function splitComposedSpec(composed, outline) {
|
|
70
|
+
const sections = new Map();
|
|
71
|
+
// Strip frontmatter (already used) and grab everything from the first '## ' onward.
|
|
72
|
+
const fmEnd = composed.indexOf("\n---\n", 4);
|
|
73
|
+
// Find the first H2.
|
|
74
|
+
const firstH2 = composed.indexOf("\n## ");
|
|
75
|
+
if (firstH2 === -1) {
|
|
76
|
+
// No areas in the composed spec — fall back to empty sections.
|
|
77
|
+
return sections;
|
|
78
|
+
}
|
|
79
|
+
const afterTitle = composed.slice(firstH2);
|
|
80
|
+
// Split on '\n---\n\n## ' so each chunk starts with '## <area name>'.
|
|
81
|
+
// (We split-include the first chunk by prepending it manually.)
|
|
82
|
+
const chunks = afterTitle.split(/\n---\n\n## /);
|
|
83
|
+
// The first chunk already starts with '\n## ' from the original; normalize:
|
|
84
|
+
chunks[0] = chunks[0].replace(/^\n?## /, "");
|
|
85
|
+
// Subsequent chunks start with the area heading text directly (we stripped '## ').
|
|
86
|
+
for (const chunk of chunks) {
|
|
87
|
+
const firstNewline = chunk.indexOf("\n");
|
|
88
|
+
if (firstNewline === -1)
|
|
89
|
+
continue;
|
|
90
|
+
const heading = chunk.slice(0, firstNewline).trim();
|
|
91
|
+
const body = chunk.slice(firstNewline + 1);
|
|
92
|
+
// Match heading to outline area (exact match by name).
|
|
93
|
+
const area = outline.areas.find((a) => a.name === heading);
|
|
94
|
+
if (area) {
|
|
95
|
+
sections.set(area.name, body.trim());
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Also handle the trivial case where compose wrote `## Area` immediately
|
|
99
|
+
// after the title (no `---` separator). Unused for current compose output
|
|
100
|
+
// but defensive.
|
|
101
|
+
// Suppress unused-var warning for fmEnd:
|
|
102
|
+
void fmEnd;
|
|
103
|
+
return sections;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a single-file requirements document from the composed spec.
|
|
107
|
+
* The composed spec IS this document (with the right frontmatter and structure).
|
|
108
|
+
* We just read it and return it.
|
|
109
|
+
*/
|
|
110
|
+
export function buildSingleFileDoc(composedSpec) {
|
|
111
|
+
return composedSpec;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a per-area requirements document for split mode.
|
|
115
|
+
*/
|
|
116
|
+
export function buildAreaDoc(outline, areaName, areaBody) {
|
|
117
|
+
const area = outline.areas.find((a) => a.name === areaName);
|
|
118
|
+
const escapedTitle = `${outline.title}: ${areaName}`
|
|
119
|
+
.replace(/\\/g, "\\\\")
|
|
120
|
+
.replace(/"/g, '\\"');
|
|
121
|
+
const parts = [
|
|
122
|
+
"---",
|
|
123
|
+
"document:",
|
|
124
|
+
` title: "${escapedTitle}"`,
|
|
125
|
+
` defaultPrefix: ${outline.defaultPrefix}`,
|
|
126
|
+
"---",
|
|
127
|
+
"",
|
|
128
|
+
`# ${outline.title}: ${areaName}`,
|
|
129
|
+
"",
|
|
130
|
+
];
|
|
131
|
+
// The planner's drive-by area.description is intentionally not surfaced
|
|
132
|
+
// here. The specifier's deeper, persona-grounded area description (written
|
|
133
|
+
// into the partial body) is the one readers see.
|
|
134
|
+
void area;
|
|
135
|
+
parts.push(areaBody.trim());
|
|
136
|
+
parts.push("");
|
|
137
|
+
return parts.join("\n");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Apply an overwrite policy to determine what action to take for a path.
|
|
141
|
+
*/
|
|
142
|
+
async function resolveOverwriteAction(policy, path) {
|
|
143
|
+
if (!existsSync(path)) {
|
|
144
|
+
// No conflict — caller will create.
|
|
145
|
+
return "overwrite";
|
|
146
|
+
}
|
|
147
|
+
switch (policy) {
|
|
148
|
+
case "overwrite":
|
|
149
|
+
return "overwrite";
|
|
150
|
+
case "skip-existing":
|
|
151
|
+
return "skip";
|
|
152
|
+
case "fail-fast":
|
|
153
|
+
return "refused";
|
|
154
|
+
case "prompt": {
|
|
155
|
+
const choice = await promptOverwriteChoice(path);
|
|
156
|
+
if (choice === "cancel")
|
|
157
|
+
return "refused";
|
|
158
|
+
return choice;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Merge new content into an existing requirements file.
|
|
164
|
+
*
|
|
165
|
+
* Strategy: append the new file's requirement blocks below the existing
|
|
166
|
+
* content, with a separator comment. Existing requirements are preserved
|
|
167
|
+
* verbatim.
|
|
168
|
+
*/
|
|
169
|
+
function mergeContent(existingPath, newContent) {
|
|
170
|
+
const existing = readFileSync(existingPath, "utf-8").trimEnd();
|
|
171
|
+
// Strip frontmatter from newContent (it would conflict with existing frontmatter)
|
|
172
|
+
const fmMatch = newContent.match(/^---\n[\s\S]+?\n---\n+/);
|
|
173
|
+
const body = fmMatch
|
|
174
|
+
? newContent.slice(fmMatch[0].length).trimStart()
|
|
175
|
+
: newContent;
|
|
176
|
+
return [
|
|
177
|
+
existing,
|
|
178
|
+
"",
|
|
179
|
+
"",
|
|
180
|
+
"<!-- Merged content appended by `dotrequirements cts present`. Review and integrate. -->",
|
|
181
|
+
"",
|
|
182
|
+
body,
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Validate a path's content against the dotrequirements schema. Returns an
|
|
187
|
+
* error message string, or undefined if valid.
|
|
188
|
+
*/
|
|
189
|
+
function validateContent(content) {
|
|
190
|
+
try {
|
|
191
|
+
parseRequirementsFile(content);
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
return err instanceof Error ? err.message : String(err);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Run the present stage.
|
|
200
|
+
*
|
|
201
|
+
* Returns a list of actions taken per path. In fail-fast mode, if any path
|
|
202
|
+
* would conflict, the function returns early with `conflictPath` set and
|
|
203
|
+
* `allWritten = false` — no files are written in this case (preserving the
|
|
204
|
+
* "no file overwritten without choice" guarantee from CTS-PRESENT-2.1).
|
|
205
|
+
*/
|
|
206
|
+
export async function runPresent(options) {
|
|
207
|
+
const { outline, finalSpecPath, projectRoot, overwritePolicy } = options;
|
|
208
|
+
const composedSpec = readFileSync(finalSpecPath, "utf-8");
|
|
209
|
+
const plan = planPresentPaths(outline, projectRoot);
|
|
210
|
+
// Compute the content for each output up front.
|
|
211
|
+
const planned = [];
|
|
212
|
+
if (plan.mode === "single") {
|
|
213
|
+
planned.push({
|
|
214
|
+
outPath: plan.paths[0].outPath,
|
|
215
|
+
content: buildSingleFileDoc(composedSpec),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const sections = splitComposedSpec(composedSpec, outline);
|
|
220
|
+
for (const { area, outPath } of plan.paths) {
|
|
221
|
+
const body = sections.get(area) ??
|
|
222
|
+
"_(no content for this area was found in the composed spec)_";
|
|
223
|
+
planned.push({ outPath, content: buildAreaDoc(outline, area, body) });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// In fail-fast mode, scan for any conflict before writing anything.
|
|
227
|
+
if (overwritePolicy === "fail-fast") {
|
|
228
|
+
for (const { outPath } of planned) {
|
|
229
|
+
if (existsSync(outPath)) {
|
|
230
|
+
return {
|
|
231
|
+
outputPaths: planned.map((p) => p.outPath),
|
|
232
|
+
actions: [],
|
|
233
|
+
allWritten: false,
|
|
234
|
+
conflictPath: outPath,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const actions = [];
|
|
240
|
+
let allWritten = true;
|
|
241
|
+
for (const { outPath, content } of planned) {
|
|
242
|
+
const choice = await resolveOverwriteAction(overwritePolicy, outPath);
|
|
243
|
+
const dir = dirname(outPath);
|
|
244
|
+
if (!existsSync(dir))
|
|
245
|
+
mkdirSync(dir, { recursive: true });
|
|
246
|
+
if (choice === "refused") {
|
|
247
|
+
// In prompt mode, the user cancelled the prompt.
|
|
248
|
+
actions.push({ path: outPath, action: "refused" });
|
|
249
|
+
allWritten = false;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (choice === "skip") {
|
|
253
|
+
actions.push({ path: outPath, action: "skipped" });
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (choice === "merge" && existsSync(outPath)) {
|
|
257
|
+
const merged = mergeContent(outPath, content);
|
|
258
|
+
const validationError = validateContent(merged);
|
|
259
|
+
if (validationError) {
|
|
260
|
+
// Fallback: leave the existing file alone and warn.
|
|
261
|
+
actions.push({ path: outPath, action: "skipped" });
|
|
262
|
+
allWritten = false;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
writeFileSync(outPath, merged, "utf-8");
|
|
266
|
+
actions.push({ path: outPath, action: "merged" });
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
// Overwrite or create
|
|
270
|
+
const validationError = validateContent(content);
|
|
271
|
+
if (validationError) {
|
|
272
|
+
allWritten = false;
|
|
273
|
+
actions.push({ path: outPath, action: "refused" });
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const action = existsSync(outPath)
|
|
277
|
+
? "overwrote"
|
|
278
|
+
: "created";
|
|
279
|
+
writeFileSync(outPath, content, "utf-8");
|
|
280
|
+
actions.push({ path: outPath, action });
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
outputPaths: planned.map((p) => p.outPath),
|
|
284
|
+
actions,
|
|
285
|
+
allWritten,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=present.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured progress emitter for codebase-to-spec.
|
|
3
|
+
*
|
|
4
|
+
* Emits one line per event to stdout. Wrappers (the skill, CI tooling) parse
|
|
5
|
+
* these lines to render progress in their own UX.
|
|
6
|
+
*
|
|
7
|
+
* Format: `[CTS] <stage>/<step> <message>` where <stage>/<step> identifies the
|
|
8
|
+
* pipeline location and <message> is human-readable.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-CLI-5: CLI emissions are part of the public contract (stable format)
|
|
12
|
+
* - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
|
|
13
|
+
*/
|
|
14
|
+
export type Stage = "pack" | "plan" | "outline-review" | "specify" | "compose" | "spec-review" | "present";
|
|
15
|
+
export interface ProgressEvent {
|
|
16
|
+
stage: Stage;
|
|
17
|
+
step?: string;
|
|
18
|
+
message: string;
|
|
19
|
+
/** Optional structured data — included on JSON lines but not on plain text lines. */
|
|
20
|
+
data?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface ProgressEmitter {
|
|
23
|
+
emit(event: ProgressEvent): void;
|
|
24
|
+
}
|
|
25
|
+
export declare class StdoutProgress implements ProgressEmitter {
|
|
26
|
+
emit(event: ProgressEvent): void;
|
|
27
|
+
}
|
|
28
|
+
/** Capturing emitter for tests. */
|
|
29
|
+
export declare class CapturingProgress implements ProgressEmitter {
|
|
30
|
+
events: ProgressEvent[];
|
|
31
|
+
emit(event: ProgressEvent): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured progress emitter for codebase-to-spec.
|
|
3
|
+
*
|
|
4
|
+
* Emits one line per event to stdout. Wrappers (the skill, CI tooling) parse
|
|
5
|
+
* these lines to render progress in their own UX.
|
|
6
|
+
*
|
|
7
|
+
* Format: `[CTS] <stage>/<step> <message>` where <stage>/<step> identifies the
|
|
8
|
+
* pipeline location and <message> is human-readable.
|
|
9
|
+
*
|
|
10
|
+
* Requirements covered:
|
|
11
|
+
* - CTS-CLI-5: CLI emissions are part of the public contract (stable format)
|
|
12
|
+
* - CTS-OBSERVE-1: CLI emits structured progress as the pipeline runs
|
|
13
|
+
*/
|
|
14
|
+
import { stdout } from "node:process";
|
|
15
|
+
export class StdoutProgress {
|
|
16
|
+
emit(event) {
|
|
17
|
+
const stagePath = event.step ? `${event.stage}/${event.step}` : event.stage;
|
|
18
|
+
stdout.write(`[CTS] ${stagePath} ${event.message}\n`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Capturing emitter for tests. */
|
|
22
|
+
export class CapturingProgress {
|
|
23
|
+
events = [];
|
|
24
|
+
emit(event) {
|
|
25
|
+
this.events.push(event);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=progress.js.map
|