@popoverai/dotrequirements 0.25.0 → 0.26.1

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.
Files changed (103) hide show
  1. package/dist/cli.js +8 -1
  2. package/dist/codebase-to-spec/area-name.d.ts +13 -0
  3. package/dist/codebase-to-spec/area-name.js +18 -0
  4. package/dist/codebase-to-spec/cache.d.ts +31 -0
  5. package/dist/codebase-to-spec/cache.js +29 -1
  6. package/dist/codebase-to-spec/compose.d.ts +7 -4
  7. package/dist/codebase-to-spec/compose.js +10 -21
  8. package/dist/codebase-to-spec/dispatch.js +1 -1
  9. package/dist/codebase-to-spec/present.d.ts +9 -0
  10. package/dist/codebase-to-spec/present.js +30 -4
  11. package/dist/codebase-to-spec/prompts/planner-initial.d.ts +3 -2
  12. package/dist/codebase-to-spec/prompts/planner-initial.js +3 -2
  13. package/dist/codebase-to-spec/renumber.d.ts +52 -0
  14. package/dist/codebase-to-spec/renumber.js +105 -0
  15. package/dist/codebase-to-spec/schemas.d.ts +35 -240
  16. package/dist/codebase-to-spec/schemas.js +5 -173
  17. package/dist/codebase-to-spec/skill-install.d.ts +16 -6
  18. package/dist/codebase-to-spec/skill-install.js +109 -47
  19. package/dist/codebase-to-spec/version-check.d.ts +31 -0
  20. package/dist/codebase-to-spec/version-check.js +56 -0
  21. package/dist/commands/ai-setup.d.ts +12 -1
  22. package/dist/commands/ai-setup.js +65 -33
  23. package/dist/commands/codebase-to-spec/dispatch-editor.js +1 -1
  24. package/dist/commands/codebase-to-spec/dispatch-spec.js +1 -1
  25. package/dist/commands/codebase-to-spec/index.js +7 -103
  26. package/dist/commands/codebase-to-spec/pack.d.ts +4 -0
  27. package/dist/commands/codebase-to-spec/pack.js +25 -1
  28. package/dist/commands/codebase-to-spec/present-orchestrator.d.ts +3 -1
  29. package/dist/commands/codebase-to-spec/present-orchestrator.js +11 -4
  30. package/dist/commands/init.js +6 -1
  31. package/dist/commands/link-resolution.d.ts +79 -0
  32. package/dist/commands/link-resolution.js +141 -0
  33. package/dist/commands/link.d.ts +14 -4
  34. package/dist/commands/link.js +369 -16
  35. package/dist/commands/pull.js +19 -2
  36. package/dist/commands/push.js +36 -2
  37. package/dist/convex.d.ts +5 -3
  38. package/dist/convex.js +5 -3
  39. package/dist/harness/cache.d.ts +0 -14
  40. package/dist/harness/cache.js +1 -41
  41. package/dist/harness/finalize.js +2 -2
  42. package/dist/harness/prepare.js +1 -3
  43. package/dist/harness/requirementsLoader.d.ts +3 -3
  44. package/dist/harness/requirementsLoader.js +13 -8
  45. package/dist/mcp/handlers/authoring.d.ts +5 -5
  46. package/dist/mcp/handlers/authoring.js +9 -9
  47. package/dist/mcp/handlers/push.d.ts +2 -2
  48. package/dist/mcp/handlers/push.js +36 -3
  49. package/dist/mcp/handlers/review.d.ts +4 -4
  50. package/dist/mcp/handlers/review.js +4 -4
  51. package/dist/mcp/handlers/search.d.ts +1 -1
  52. package/dist/mcp/handlers/search.js +1 -1
  53. package/dist/mcp/index.js +29 -0
  54. package/dist/push/core.d.ts +18 -0
  55. package/dist/push/core.js +70 -3
  56. package/dist/push/index.d.ts +1 -1
  57. package/dist/push/index.js +1 -1
  58. package/dist/schema/parser-core.js +5 -1
  59. package/dist/schema/parser.js +5 -1
  60. package/dist/schema/run-marker.d.ts +38 -0
  61. package/dist/schema/run-marker.js +138 -0
  62. package/dist/schema/schemas.d.ts +12 -0
  63. package/dist/schema/schemas.js +1 -0
  64. package/dist/templates/agents/cts-worker.md +1 -1
  65. package/dist/templates/skills/codebase-to-spec/SKILL.md +39 -11
  66. package/dist/templates/workflows/specify-codebase.js +4 -2
  67. package/dist/utils/own-package.d.ts +10 -0
  68. package/dist/utils/own-package.js +13 -0
  69. package/dist/utils/project-selector.d.ts +5 -0
  70. package/dist/utils/project-selector.js +4 -0
  71. package/package.json +2 -2
  72. package/dist/codebase-to-spec/edit-loop.d.ts +0 -54
  73. package/dist/codebase-to-spec/edit-loop.js +0 -195
  74. package/dist/codebase-to-spec/editor.d.ts +0 -54
  75. package/dist/codebase-to-spec/editor.js +0 -74
  76. package/dist/codebase-to-spec/fan-out.d.ts +0 -63
  77. package/dist/codebase-to-spec/fan-out.js +0 -215
  78. package/dist/codebase-to-spec/outline-review-loop.d.ts +0 -51
  79. package/dist/codebase-to-spec/outline-review-loop.js +0 -187
  80. package/dist/codebase-to-spec/planner.d.ts +0 -41
  81. package/dist/codebase-to-spec/planner.js +0 -76
  82. package/dist/codebase-to-spec/prompts/outline-reviewer.d.ts +0 -12
  83. package/dist/codebase-to-spec/prompts/outline-reviewer.js +0 -89
  84. package/dist/codebase-to-spec/slice.d.ts +0 -49
  85. package/dist/codebase-to-spec/slice.js +0 -111
  86. package/dist/codebase-to-spec/specifier.d.ts +0 -60
  87. package/dist/codebase-to-spec/specifier.js +0 -85
  88. package/dist/codebase-to-spec/summary.d.ts +0 -51
  89. package/dist/codebase-to-spec/summary.js +0 -183
  90. package/dist/commands/codebase-to-spec/compose.d.ts +0 -14
  91. package/dist/commands/codebase-to-spec/compose.js +0 -57
  92. package/dist/commands/codebase-to-spec/edit-loop.d.ts +0 -16
  93. package/dist/commands/codebase-to-spec/edit-loop.js +0 -83
  94. package/dist/commands/codebase-to-spec/fan-out.d.ts +0 -19
  95. package/dist/commands/codebase-to-spec/fan-out.js +0 -77
  96. package/dist/commands/codebase-to-spec/plan-loop.d.ts +0 -26
  97. package/dist/commands/codebase-to-spec/plan-loop.js +0 -105
  98. package/dist/commands/codebase-to-spec/present.d.ts +0 -26
  99. package/dist/commands/codebase-to-spec/present.js +0 -97
  100. package/dist/commands/codebase-to-spec/run.d.ts +0 -20
  101. package/dist/commands/codebase-to-spec/run.js +0 -86
  102. package/dist/commands/codebase-to-spec/specify-area.d.ts +0 -18
  103. package/dist/commands/codebase-to-spec/specify-area.js +0 -82
package/dist/cli.js CHANGED
@@ -64,7 +64,13 @@ program
64
64
  .action(wrapCommand(initCommand));
65
65
  program
66
66
  .command("link")
67
- .description("Link local environment to an existing project")
67
+ .description("Link local environment to a cloud project (creating one when needed)")
68
+ .option("-y, --yes", "Non-interactive: resolve every decision from defaults or flags; never prompt")
69
+ .option("--json", "Machine-readable output (one JSON object on stdout)")
70
+ .option("--team <nameOrId>", "Use this team (implies --yes)")
71
+ .option("--connect <slug>", "Connect to this existing project (implies --yes)")
72
+ .option("--create", "Create a new project (implies --yes)")
73
+ .option("-n, --name <name>", "Project name for --create or rename")
68
74
  .action(wrapCommand(linkCommand));
69
75
  program
70
76
  .command("pull")
@@ -125,6 +131,7 @@ program
125
131
  program
126
132
  .command("ai-setup")
127
133
  .description("Configure MCP server for your AI assistant (Claude Code, Claude Desktop, etc.)")
134
+ .option("-a, --assistant <id>", "Configure for this assistant without prompting (e.g. claude-code)")
128
135
  .action(wrapCommand(aiSetupCommand));
129
136
  program
130
137
  .command("search <query>")
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Area-name slugging shared across the codebase-to-spec stages.
3
+ *
4
+ * The pack/specify/compose/present stages and the worker dispatch surface all
5
+ * derive cache and output file names from an area's display name; they agree on
6
+ * the slug by routing through this single helper.
7
+ */
8
+ /**
9
+ * Convert an area name to a stable filesystem-safe identifier.
10
+ * E.g., "Reading & Editing Files" → "reading-editing-files"
11
+ */
12
+ export declare function sanitizeAreaName(name: string): string;
13
+ //# sourceMappingURL=area-name.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Area-name slugging shared across the codebase-to-spec stages.
3
+ *
4
+ * The pack/specify/compose/present stages and the worker dispatch surface all
5
+ * derive cache and output file names from an area's display name; they agree on
6
+ * the slug by routing through this single helper.
7
+ */
8
+ /**
9
+ * Convert an area name to a stable filesystem-safe identifier.
10
+ * E.g., "Reading & Editing Files" → "reading-editing-files"
11
+ */
12
+ export function sanitizeAreaName(name) {
13
+ return (name
14
+ .replace(/[^A-Za-z0-9]+/g, "-")
15
+ .replace(/^-+|-+$/g, "")
16
+ .toLowerCase() || "area");
17
+ }
18
+ //# sourceMappingURL=area-name.js.map
@@ -44,8 +44,39 @@ export interface CachePaths {
44
44
  * lifecycle state inline. Distinct from the legacy outline-N.json files.
45
45
  */
46
46
  outline: string;
47
+ /**
48
+ * Persisted run-config marker (e.g., the isolation decision). Written once
49
+ * at pack time and read by later stages, so a choice like isolation is
50
+ * honored end-to-end without the caller resupplying it. See CTS-PRESENT-5.2.
51
+ */
52
+ runConfig: string;
47
53
  }
48
54
  export declare function cachePaths(projectRoot: string): CachePaths;
55
+ /**
56
+ * Per-run configuration persisted in the cache so a decision made at one stage
57
+ * is honored by later stages. The isolation decision (CTS-PRESENT-5) lives here:
58
+ * pack records it once, and the present stage reads it rather than relying on
59
+ * the caller — or the orchestrating agent's memory — to carry it across the run.
60
+ */
61
+ export interface RunConfig {
62
+ /**
63
+ * Whether this run is isolated from an existing `.requirements/` directory:
64
+ * the pack excluded it, and the present stage writes under `.requirements/cts/`.
65
+ */
66
+ isolated: boolean;
67
+ }
68
+ /**
69
+ * Read the persisted run-config. Returns a non-isolated default when the marker
70
+ * is absent (an older cache, or a run that never wrote one) or unreadable, so a
71
+ * missing marker can never silently isolate output.
72
+ */
73
+ export declare function readRunConfig(projectRoot: string): RunConfig;
74
+ /**
75
+ * Persist the run-config. Pack writes this on every invocation (with the current
76
+ * isolation value) so a re-pack without `--ignore-requirements` clears a stale
77
+ * isolated marker rather than leaving the prior run's choice in place.
78
+ */
79
+ export declare function writeRunConfig(projectRoot: string, config: RunConfig): void;
49
80
  export declare function ensureCacheDir(projectRoot: string): CachePaths;
50
81
  /**
51
82
  * Remove all cached artifacts. Used by `--fresh`.
@@ -10,7 +10,7 @@
10
10
  * - CTS-PACK-1: pack stage paths (overview.txt, source.txt)
11
11
  * - CTS-RESUME-1..4: per-stage cache detection
12
12
  */
13
- import { existsSync, mkdirSync, rmSync } from "node:fs";
13
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  export const CACHE_DIR_NAME = ".dotrequirements-cache";
16
16
  export function cachePaths(projectRoot) {
@@ -30,8 +30,36 @@ export function cachePaths(projectRoot) {
30
30
  specFinal: join(root, "spec-final.md"),
31
31
  pipelineSummary: join(root, "pipeline-summary.json"),
32
32
  outline: join(root, "outline.yaml"),
33
+ runConfig: join(root, "run-config.json"),
33
34
  };
34
35
  }
36
+ const DEFAULT_RUN_CONFIG = { isolated: false };
37
+ /**
38
+ * Read the persisted run-config. Returns a non-isolated default when the marker
39
+ * is absent (an older cache, or a run that never wrote one) or unreadable, so a
40
+ * missing marker can never silently isolate output.
41
+ */
42
+ export function readRunConfig(projectRoot) {
43
+ const path = cachePaths(projectRoot).runConfig;
44
+ if (!existsSync(path))
45
+ return { ...DEFAULT_RUN_CONFIG };
46
+ try {
47
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
48
+ return { isolated: parsed?.isolated === true };
49
+ }
50
+ catch {
51
+ return { ...DEFAULT_RUN_CONFIG };
52
+ }
53
+ }
54
+ /**
55
+ * Persist the run-config. Pack writes this on every invocation (with the current
56
+ * isolation value) so a re-pack without `--ignore-requirements` clears a stale
57
+ * isolated marker rather than leaving the prior run's choice in place.
58
+ */
59
+ export function writeRunConfig(projectRoot, config) {
60
+ const paths = ensureCacheDir(projectRoot);
61
+ writeFileSync(paths.runConfig, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
62
+ }
35
63
  export function ensureCacheDir(projectRoot) {
36
64
  const paths = cachePaths(projectRoot);
37
65
  if (!existsSync(paths.root)) {
@@ -5,13 +5,15 @@
5
5
  * title, a summary paragraph from the outline, and one H2 section per area.
6
6
  * Each area section contains the partial's content.
7
7
  *
8
- * After assembly, schema validation runs over the document. Mechanical errors
9
- * (duplicate IDs within an area, etc.) trigger deterministic fixes; other
10
- * errors surface to the caller.
8
+ * After assembly, a deterministic renumbering pass rewrites each prefix's
9
+ * requirement IDs into a clean 1..N sequence (sweeping the ID-sequence scars
10
+ * specifier/edit rounds leave behind), then schema validation runs over the
11
+ * document. Errors validation can't resolve surface to the caller.
11
12
  *
12
13
  * Requirements covered:
13
14
  * - CTS-COMPOSE-1: Compose produces a single Markdown document from partials
14
15
  * - CTS-COMPOSE-2: Compose validates the resulting document and corrects mechanical errors
16
+ * - CTS-COMPOSE-3: Compose renumbers requirement IDs into a clean sequential order
15
17
  */
16
18
  import type { Outline } from "./schemas.js";
17
19
  export interface ComposeOptions {
@@ -43,7 +45,8 @@ export declare function assembleComposedDocument(outline: Outline, partialPathFo
43
45
  partialsMissing: number;
44
46
  };
45
47
  /**
46
- * Compose the spec and write it to disk. Validates the result.
48
+ * Compose the spec and write it to disk. Renumbers requirement IDs into a
49
+ * clean sequence (CTS-COMPOSE-3), then validates the result.
47
50
  */
48
51
  export declare function runCompose(options: ComposeOptions): ComposeResult;
49
52
  //# sourceMappingURL=compose.d.ts.map
@@ -5,17 +5,20 @@
5
5
  * title, a summary paragraph from the outline, and one H2 section per area.
6
6
  * Each area section contains the partial's content.
7
7
  *
8
- * After assembly, schema validation runs over the document. Mechanical errors
9
- * (duplicate IDs within an area, etc.) trigger deterministic fixes; other
10
- * errors surface to the caller.
8
+ * After assembly, a deterministic renumbering pass rewrites each prefix's
9
+ * requirement IDs into a clean 1..N sequence (sweeping the ID-sequence scars
10
+ * specifier/edit rounds leave behind), then schema validation runs over the
11
+ * document. Errors validation can't resolve surface to the caller.
11
12
  *
12
13
  * Requirements covered:
13
14
  * - CTS-COMPOSE-1: Compose produces a single Markdown document from partials
14
15
  * - CTS-COMPOSE-2: Compose validates the resulting document and corrects mechanical errors
16
+ * - CTS-COMPOSE-3: Compose renumbers requirement IDs into a clean sequential order
15
17
  */
16
18
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
19
  import { parseRequirementsFile } from "../schema/parser.js";
18
- import { sanitizeAreaName } from "./fan-out.js";
20
+ import { sanitizeAreaName } from "./area-name.js";
21
+ import { renumberRequirements } from "./renumber.js";
19
22
  /**
20
23
  * Read a partial file. If missing or marked as a missing-partial placeholder,
21
24
  * return null (caller will emit a stub section).
@@ -84,27 +87,13 @@ function escapeYamlString(s) {
84
87
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
85
88
  }
86
89
  /**
87
- * Attempt to fix mechanical errors in a composed document.
88
- *
89
- * For v0, the simplest mechanical fix is duplicate IDs within an area —
90
- * we detect and renumber them sequentially. Position-path and Gherkin syntax
91
- * errors surface to the validator's caller; we don't auto-fix those because
92
- * the editor loop is better suited to it.
93
- */
94
- function fixMechanicalErrors(content) {
95
- // Detect duplicate IDs (lines like "PREFIX-AREA-001: ...") within each area.
96
- // For v0 we punt — the schema validator will catch them and the caller will
97
- // surface to the user. Returning content unchanged keeps the contract: the
98
- // function is a no-op for inputs without fixable errors.
99
- return content;
100
- }
101
- /**
102
- * Compose the spec and write it to disk. Validates the result.
90
+ * Compose the spec and write it to disk. Renumbers requirement IDs into a
91
+ * clean sequence (CTS-COMPOSE-3), then validates the result.
103
92
  */
104
93
  export function runCompose(options) {
105
94
  const { outline, partialPathFor, composedPath } = options;
106
95
  const { content, partialsIncluded, partialsMissing } = assembleComposedDocument(outline, partialPathFor);
107
- const finalContent = fixMechanicalErrors(content);
96
+ const finalContent = renumberRequirements(content);
108
97
  writeFileSync(composedPath, finalContent, "utf-8");
109
98
  // Validate
110
99
  let validationError;
@@ -29,8 +29,8 @@
29
29
  */
30
30
  import { existsSync, readFileSync } from "node:fs";
31
31
  import { findProjectRoot } from "../utils/project-settings.js";
32
+ import { sanitizeAreaName } from "./area-name.js";
32
33
  import { cachePaths } from "./cache.js";
33
- import { sanitizeAreaName } from "./fan-out.js";
34
34
  import { EDITOR_PROMPT } from "./prompts/editor.js";
35
35
  import { PLANNER_INITIAL_PROMPT } from "./prompts/planner-initial.js";
36
36
  import { PLANNER_REVISE_PROMPT } from "./prompts/planner-revise.js";
@@ -85,6 +85,15 @@ export declare function splitComposedSpec(composed: string, outline: Outline): M
85
85
  * We just read it and return it.
86
86
  */
87
87
  export declare function buildSingleFileDoc(composedSpec: string): string;
88
+ /**
89
+ * Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
90
+ *
91
+ * At push time, a valid marker on a newly-created document grants its rows
92
+ * `imported` authorship — exempt from the requirement limit (IMPORT-2).
93
+ * Returns the content unchanged when it has no frontmatter block (defensive;
94
+ * present output always has one).
95
+ */
96
+ export declare function stampRunMarker(content: string, marker: string): string;
88
97
  /**
89
98
  * Build a per-area requirements document for split mode.
90
99
  */
@@ -16,8 +16,10 @@
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
18
  import { parseRequirementsFile } from "../schema/parser.js";
19
- import { sanitizeAreaName } from "./fan-out.js";
19
+ import { generateRunMarker } from "../schema/run-marker.js";
20
+ import { sanitizeAreaName } from "./area-name.js";
20
21
  import { promptOverwriteChoice } from "./interactive.js";
22
+ import { renumberRequirements } from "./renumber.js";
21
23
  export const AREA_SPLIT_THRESHOLD = 5;
22
24
  /**
23
25
  * Resolve the output path(s) the present stage would write to.
@@ -113,6 +115,20 @@ export function splitComposedSpec(composed, outline) {
113
115
  export function buildSingleFileDoc(composedSpec) {
114
116
  return composedSpec;
115
117
  }
118
+ /**
119
+ * Insert the run marker as a top-level frontmatter field (IMPORT-1.0).
120
+ *
121
+ * At push time, a valid marker on a newly-created document grants its rows
122
+ * `imported` authorship — exempt from the requirement limit (IMPORT-2).
123
+ * Returns the content unchanged when it has no frontmatter block (defensive;
124
+ * present output always has one).
125
+ */
126
+ export function stampRunMarker(content, marker) {
127
+ if (!content.startsWith("---\n")) {
128
+ return content;
129
+ }
130
+ return `---\nctsRun: ${marker}\n${content.slice(4)}`;
131
+ }
116
132
  /**
117
133
  * Build a per-area requirements document for split mode.
118
134
  */
@@ -208,14 +224,21 @@ function validateContent(content) {
208
224
  */
209
225
  export async function runPresent(options) {
210
226
  const { outline, finalSpecPath, projectRoot, overwritePolicy, outputSubdir } = options;
211
- const composedSpec = readFileSync(finalSpecPath, "utf-8");
227
+ // Renumber IDs one last time before writing out (CTS-COMPOSE-3.5): the
228
+ // cross-area review/edit pass that runs between compose and present can
229
+ // reintroduce ID-sequence scars (merges leave gaps, edits add suffixes), so
230
+ // this sweep guarantees the presented spec carries a clean sequence.
231
+ const composedSpec = renumberRequirements(readFileSync(finalSpecPath, "utf-8"));
212
232
  const plan = planPresentPaths(outline, projectRoot, outputSubdir);
213
233
  // Compute the content for each output up front.
234
+ // IMPORT-1.0/1.1: one run marker per present invocation, stamped into every
235
+ // document this run produces.
236
+ const runMarker = generateRunMarker();
214
237
  const planned = [];
215
238
  if (plan.mode === "single") {
216
239
  planned.push({
217
240
  outPath: plan.paths[0].outPath,
218
- content: buildSingleFileDoc(composedSpec),
241
+ content: stampRunMarker(buildSingleFileDoc(composedSpec), runMarker),
219
242
  });
220
243
  }
221
244
  else {
@@ -223,7 +246,10 @@ export async function runPresent(options) {
223
246
  for (const { area, outPath } of plan.paths) {
224
247
  const body = sections.get(area) ??
225
248
  "_(no content for this area was found in the composed spec)_";
226
- planned.push({ outPath, content: buildAreaDoc(outline, area, body) });
249
+ planned.push({
250
+ outPath,
251
+ content: stampRunMarker(buildAreaDoc(outline, area, body), runMarker),
252
+ });
227
253
  }
228
254
  }
229
255
  // In fail-fast mode, scan for any conflict before writing anything.
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * System prompt for the initial planner pass.
3
3
  *
4
- * Reads a compressed packed view of a codebase and produces an outline JSON.
5
- * Output is constrained by `OUTLINE_JSON_SCHEMA` via `claude -p --json-schema`.
4
+ * Reads a compressed packed view of a codebase and produces the behavioral-area
5
+ * outline. The worker dispatch supplies the exact output format and path; the
6
+ * outline's shape is the `CTS-PLAN-1` contract.
6
7
  *
7
8
  * Requirements covered:
8
9
  * - CTS-PLAN-1: Planner produces a behavioral outline from the compressed pack
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * System prompt for the initial planner pass.
3
3
  *
4
- * Reads a compressed packed view of a codebase and produces an outline JSON.
5
- * Output is constrained by `OUTLINE_JSON_SCHEMA` via `claude -p --json-schema`.
4
+ * Reads a compressed packed view of a codebase and produces the behavioral-area
5
+ * outline. The worker dispatch supplies the exact output format and path; the
6
+ * outline's shape is the `CTS-PLAN-1` contract.
6
7
  *
7
8
  * Requirements covered:
8
9
  * - CTS-PLAN-1: Planner produces a behavioral outline from the compressed pack
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Deterministic requirement-ID renumbering for composed CTS specs.
3
+ *
4
+ * Review and edit rounds leave ID-sequence scars behind: out-of-order,
5
+ * skipped, and letter-suffixed requirement numbers (`HONO-CTX-50` wedged
6
+ * between 16 and 17, `AUTH-PWD-12B`, `CUTIL-11a`). They are functionally
7
+ * harmless but read as sloppiness. This module rewrites each prefix's IDs
8
+ * into a clean, contiguous 1..N sequence in document order, and rewrites prose
9
+ * cross-references to match.
10
+ *
11
+ * It runs at two deterministic chokepoints (CTS-COMPOSE-3.0, CTS-COMPOSE-3.5):
12
+ * - compose, before the cross-area review pass reads the spec (so reviewers
13
+ * cite final IDs);
14
+ * - present, after that pass, so scars the review/edit pass introduces are
15
+ * swept before the spec is written out.
16
+ *
17
+ * It operates on raw text rather than the parsed tree on purpose: the dirty
18
+ * input (letter-suffixed keys) fails strict key validation in the parser — the
19
+ * scars are exactly what we need to clean before validation can pass. Working
20
+ * on text also keeps the blast radius tiny: only ID tokens change, everything
21
+ * else (prose, formatting, position paths) is preserved.
22
+ *
23
+ * Requirements covered:
24
+ * - CTS-COMPOSE-3: Compose renumbers requirement IDs into a clean sequential order
25
+ */
26
+ export interface Rename {
27
+ /** The original ID exactly as it appears in the document. */
28
+ from: string;
29
+ /** The clean, sequential replacement ID. */
30
+ to: string;
31
+ }
32
+ /**
33
+ * Scan top-level requirement headers in document order and assign each prefix
34
+ * a contiguous 1..N sequence. Returns the rename list (including identity
35
+ * renames for already-clean IDs, so callers can treat the result uniformly).
36
+ *
37
+ * Only the first line of each ```dotrequirements block is treated as a header
38
+ * (one requirement per block, matching the parser's contract). Exact-duplicate
39
+ * keys are a distinct validation error and out of scope here: a repeated key
40
+ * does not consume a sequence slot (so it introduces no phantom gap) and keeps
41
+ * its first mapping, leaving the downstream validator to flag the duplicate.
42
+ */
43
+ export declare function computeRenames(content: string): Rename[];
44
+ /**
45
+ * Renumber every requirement ID in a composed spec into a clean, contiguous
46
+ * 1..N sequence per prefix, rewriting prose cross-references to match.
47
+ *
48
+ * Deterministic and idempotent: on an already-clean spec the rename map is the
49
+ * identity, so the output is byte-for-byte identical (CTS-COMPOSE-3.6).
50
+ */
51
+ export declare function renumberRequirements(content: string): string;
52
+ //# sourceMappingURL=renumber.d.ts.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Deterministic requirement-ID renumbering for composed CTS specs.
3
+ *
4
+ * Review and edit rounds leave ID-sequence scars behind: out-of-order,
5
+ * skipped, and letter-suffixed requirement numbers (`HONO-CTX-50` wedged
6
+ * between 16 and 17, `AUTH-PWD-12B`, `CUTIL-11a`). They are functionally
7
+ * harmless but read as sloppiness. This module rewrites each prefix's IDs
8
+ * into a clean, contiguous 1..N sequence in document order, and rewrites prose
9
+ * cross-references to match.
10
+ *
11
+ * It runs at two deterministic chokepoints (CTS-COMPOSE-3.0, CTS-COMPOSE-3.5):
12
+ * - compose, before the cross-area review pass reads the spec (so reviewers
13
+ * cite final IDs);
14
+ * - present, after that pass, so scars the review/edit pass introduces are
15
+ * swept before the spec is written out.
16
+ *
17
+ * It operates on raw text rather than the parsed tree on purpose: the dirty
18
+ * input (letter-suffixed keys) fails strict key validation in the parser — the
19
+ * scars are exactly what we need to clean before validation can pass. Working
20
+ * on text also keeps the blast radius tiny: only ID tokens change, everything
21
+ * else (prose, formatting, position paths) is preserved.
22
+ *
23
+ * Requirements covered:
24
+ * - CTS-COMPOSE-3: Compose renumbers requirement IDs into a clean sequential order
25
+ */
26
+ /**
27
+ * A top-level requirement header line: `PREFIX-NUM[suffix]: ...`.
28
+ * The prefix is letters/hyphens (no digits — digits only appear in the number),
29
+ * so the first `-<digits>` after the prefix is unambiguously the numeric tail.
30
+ * A trailing letter run (`12B`, `11a`) is an editing scar we discard.
31
+ */
32
+ const HEADER_RE = /^([A-Z][A-Z-]*?)-(\d+)([A-Za-z]*)\s*:/;
33
+ /** A ```dotrequirements fenced block; group 1 is the block body. Tolerates
34
+ * CRLF so a `\r\n`-encoded spec still renumbers rather than silently no-op'ing. */
35
+ const BLOCK_RE = /```dotrequirements\r?\n([\s\S]*?)```/g;
36
+ function escapeRegExp(s) {
37
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
38
+ }
39
+ /**
40
+ * Scan top-level requirement headers in document order and assign each prefix
41
+ * a contiguous 1..N sequence. Returns the rename list (including identity
42
+ * renames for already-clean IDs, so callers can treat the result uniformly).
43
+ *
44
+ * Only the first line of each ```dotrequirements block is treated as a header
45
+ * (one requirement per block, matching the parser's contract). Exact-duplicate
46
+ * keys are a distinct validation error and out of scope here: a repeated key
47
+ * does not consume a sequence slot (so it introduces no phantom gap) and keeps
48
+ * its first mapping, leaving the downstream validator to flag the duplicate.
49
+ */
50
+ export function computeRenames(content) {
51
+ const counters = new Map();
52
+ const seen = new Set();
53
+ const renames = [];
54
+ BLOCK_RE.lastIndex = 0;
55
+ let block = BLOCK_RE.exec(content);
56
+ while (block !== null) {
57
+ const body = block[1];
58
+ const firstLine = body.split("\n").find((l) => l.trim().length > 0) ?? "";
59
+ const m = firstLine.match(HEADER_RE);
60
+ if (m) {
61
+ const [, prefix, num, suffix] = m;
62
+ const from = `${prefix}-${num}${suffix}`;
63
+ if (!seen.has(from)) {
64
+ seen.add(from);
65
+ const next = (counters.get(prefix) ?? 0) + 1;
66
+ counters.set(prefix, next);
67
+ renames.push({ from, to: `${prefix}-${next}` });
68
+ }
69
+ }
70
+ block = BLOCK_RE.exec(content);
71
+ }
72
+ return renames;
73
+ }
74
+ /**
75
+ * Renumber every requirement ID in a composed spec into a clean, contiguous
76
+ * 1..N sequence per prefix, rewriting prose cross-references to match.
77
+ *
78
+ * Deterministic and idempotent: on an already-clean spec the rename map is the
79
+ * identity, so the output is byte-for-byte identical (CTS-COMPOSE-3.6).
80
+ */
81
+ export function renumberRequirements(content) {
82
+ const renames = computeRenames(content);
83
+ if (renames.length === 0)
84
+ return content;
85
+ const map = new Map(renames.map((r) => [r.from, r.to]));
86
+ // One combined, single-pass replacement so each ID location is rewritten
87
+ // exactly once — no risk of a freshly-written new ID being re-matched.
88
+ // Longest-first alternation so a shorter key can't shadow a longer one that
89
+ // shares its text. The lookbehind rejects a preceding letter/digit/hyphen so
90
+ // we never match the tail of a longer hyphenated token (e.g. `CTX-5` inside
91
+ // `HONO-CTX-5`); the lookahead mirrors it, rejecting a following
92
+ // letter/digit/hyphen so `AUTH-3` never matches inside `AUTH-30`, `AUTH-3B`,
93
+ // or a hyphen-joined token, while a following `.` (a child position path like
94
+ // `AUTH-3.2`) is preserved.
95
+ const alternation = renames
96
+ .map((r) => r.from)
97
+ .sort((a, b) => b.length - a.length)
98
+ .map(escapeRegExp)
99
+ .join("|");
100
+ const re = new RegExp(`(?<![A-Za-z0-9-])(${alternation})(?![A-Za-z0-9-])`, "g");
101
+ // Cross-references whose ID isn't an existing top-level requirement aren't in
102
+ // the map and so are never matched — they're left unchanged (CTS-COMPOSE-3.4).
103
+ return content.replace(re, (matched) => map.get(matched) ?? matched);
104
+ }
105
+ //# sourceMappingURL=renumber.js.map