cclaw-cli 1.0.0 → 3.0.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.
Files changed (52) hide show
  1. package/dist/artifact-linter/brainstorm.js +15 -1
  2. package/dist/artifact-linter/design.js +14 -0
  3. package/dist/artifact-linter/scope.js +14 -0
  4. package/dist/artifact-linter/shared.d.ts +1 -0
  5. package/dist/artifact-linter/shared.js +32 -0
  6. package/dist/artifact-linter.js +13 -5
  7. package/dist/cli.js +2 -9
  8. package/dist/config.d.ts +11 -67
  9. package/dist/config.js +59 -649
  10. package/dist/content/hook-events.js +1 -5
  11. package/dist/content/hook-manifest.d.ts +6 -4
  12. package/dist/content/hook-manifest.js +16 -65
  13. package/dist/content/hooks.js +54 -14
  14. package/dist/content/meta-skill.js +4 -3
  15. package/dist/content/node-hooks.d.ts +0 -26
  16. package/dist/content/node-hooks.js +459 -157
  17. package/dist/content/observe.js +5 -4
  18. package/dist/content/opencode-plugin.js +1 -78
  19. package/dist/content/skills-elicitation.d.ts +1 -0
  20. package/dist/content/skills-elicitation.js +123 -0
  21. package/dist/content/skills.js +6 -4
  22. package/dist/content/stages/brainstorm.js +7 -3
  23. package/dist/content/stages/design.js +6 -2
  24. package/dist/content/stages/plan.js +2 -2
  25. package/dist/content/stages/scope.js +9 -5
  26. package/dist/content/stages/tdd.js +11 -11
  27. package/dist/content/start-command.js +4 -4
  28. package/dist/content/templates.js +21 -0
  29. package/dist/flow-state.d.ts +7 -0
  30. package/dist/flow-state.js +1 -0
  31. package/dist/gate-evidence.js +1 -5
  32. package/dist/hook-schema.js +3 -0
  33. package/dist/hook-schemas/claude-hooks.v1.json +2 -5
  34. package/dist/hook-schemas/codex-hooks.v1.json +1 -4
  35. package/dist/hook-schemas/cursor-hooks.v1.json +1 -3
  36. package/dist/install.d.ts +2 -7
  37. package/dist/install.js +32 -123
  38. package/dist/internal/advance-stage/advance.js +22 -1
  39. package/dist/internal/advance-stage/parsers.d.ts +1 -0
  40. package/dist/internal/advance-stage/parsers.js +6 -0
  41. package/dist/internal/compound-readiness.js +1 -16
  42. package/dist/internal/early-loop-status.js +1 -3
  43. package/dist/internal/runtime-integrity.js +0 -20
  44. package/dist/policy.js +6 -9
  45. package/dist/run-persistence.d.ts +1 -1
  46. package/dist/run-persistence.js +29 -2
  47. package/dist/runtime/run-hook.mjs +459 -265
  48. package/dist/tdd-verification-evidence.js +6 -18
  49. package/dist/track-heuristics.d.ts +7 -1
  50. package/dist/track-heuristics.js +12 -0
  51. package/dist/types.d.ts +0 -56
  52. package/package.json +1 -1
@@ -1,8 +1,22 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { checkCriticPredictionsContract, sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
3
+ import { checkCriticPredictionsContract, sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, getMarkdownTableRows, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
4
4
  export async function lintBrainstormStage(ctx) {
5
5
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
6
+ const qaLogBody = sectionBodyByName(sections, "Q&A Log");
7
+ const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
8
+ const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
9
+ findings.push({
10
+ section: "qa_log_missing",
11
+ required: false,
12
+ rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
13
+ found: qaLogOk,
14
+ details: qaLogOk
15
+ ? `Q&A Log contains ${qaLogRows.length} data row(s).`
16
+ : qaLogBody === null
17
+ ? "Missing `## Q&A Log` section."
18
+ : "Q&A Log is present but has zero data rows."
19
+ });
6
20
  // Brainstorm Iron Law: "NO ARTIFACT IS COMPLETE WITHOUT AN EXPLICITLY
7
21
  // APPROVED DIRECTION — SILENCE IS NOT APPROVAL." Previously this was
8
22
  // prose-only — nothing failed when the Selected Direction section
@@ -204,6 +204,20 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
204
204
  }
205
205
  export async function lintDesignStage(ctx) {
206
206
  const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
207
+ const qaLogBody = sectionBodyByName(sections, "Q&A Log");
208
+ const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
209
+ const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
210
+ findings.push({
211
+ section: "qa_log_missing",
212
+ required: false,
213
+ rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
214
+ found: qaLogOk,
215
+ details: qaLogOk
216
+ ? `Q&A Log contains ${qaLogRows.length} data row(s).`
217
+ : qaLogBody === null
218
+ ? "Missing `## Q&A Log` section."
219
+ : "Q&A Log is present but has zero data rows."
220
+ });
207
221
  const criticPredictions = checkCriticPredictionsContract(sections);
208
222
  if (criticPredictions !== null) {
209
223
  findings.push({
@@ -12,6 +12,20 @@ export async function lintScopeStage(ctx) {
12
12
  sectionBodyByName(sections, "Scope Summary") ?? "",
13
13
  lockedDecisionsBody
14
14
  ].join("\n");
15
+ const qaLogBody = sectionBodyByName(sections, "Q&A Log");
16
+ const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
17
+ const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
18
+ findings.push({
19
+ section: "qa_log_missing",
20
+ required: false,
21
+ rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
22
+ found: qaLogOk,
23
+ details: qaLogOk
24
+ ? `Q&A Log contains ${qaLogRows.length} data row(s).`
25
+ : qaLogBody === null
26
+ ? "Missing `## Q&A Log` section."
27
+ : "Q&A Log is present but has zero data rows."
28
+ });
15
29
  const strategistRequired = selectedScopeMode === "SCOPE EXPANSION" || selectedScopeMode === "SELECTIVE EXPANSION";
16
30
  if (strategistRequired) {
17
31
  const delegationLedger = await readDelegationLedger(projectRoot);
@@ -26,6 +26,7 @@ export type H2SectionMap = Map<string, string>;
26
26
  * into multiple passes.
27
27
  */
28
28
  export declare function extractH2Sections(markdown: string): H2SectionMap;
29
+ export declare function duplicateH2Headings(markdown: string): string[];
29
30
  export declare function headingPresent(sections: H2SectionMap, section: string): boolean;
30
31
  export declare function sectionBodyByName(sections: H2SectionMap, section: string): string | null;
31
32
  export declare function sectionBodyByAnyName(sections: H2SectionMap, sectionNames: string[]): string | null;
@@ -57,6 +57,38 @@ export function extractH2Sections(markdown) {
57
57
  flush();
58
58
  return sections;
59
59
  }
60
+ export function duplicateH2Headings(markdown) {
61
+ const lines = markdown.split(/\r?\n/);
62
+ let fenced = null;
63
+ const counts = new Map();
64
+ const displayHeading = new Map();
65
+ for (const line of lines) {
66
+ const fenceMatch = /^(```|~~~)/u.exec(line);
67
+ if (fenceMatch) {
68
+ if (fenced === null) {
69
+ fenced = fenceMatch[1] ?? null;
70
+ }
71
+ else if (line.startsWith(fenced)) {
72
+ fenced = null;
73
+ }
74
+ continue;
75
+ }
76
+ if (fenced !== null)
77
+ continue;
78
+ const match = /^##\s+(.+)$/u.exec(line);
79
+ if (!match)
80
+ continue;
81
+ const heading = normalizeHeadingTitle(match[1] ?? "");
82
+ const key = heading.toLowerCase();
83
+ counts.set(key, (counts.get(key) ?? 0) + 1);
84
+ if (!displayHeading.has(key)) {
85
+ displayHeading.set(key, heading);
86
+ }
87
+ }
88
+ return [...counts.entries()]
89
+ .filter(([, count]) => count > 1)
90
+ .map(([key]) => displayHeading.get(key) ?? key);
91
+ }
60
92
  export function headingPresent(sections, section) {
61
93
  const want = normalizeHeadingTitle(section).toLowerCase();
62
94
  for (const h of sections.keys()) {
@@ -1,9 +1,8 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-paths.js";
3
- import { readConfig } from "./config.js";
4
3
  import { exists } from "./fs-utils.js";
5
4
  import { stageSchema } from "./content/stage-schema.js";
6
- import { extractH2Sections, extractLockedDecisionAnchors, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
5
+ import { duplicateH2Headings, extractH2Sections, extractLockedDecisionAnchors, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
7
6
  import { lintBrainstormStage } from "./artifact-linter/brainstorm.js";
8
7
  import { lintDesignStage } from "./artifact-linter/design.js";
9
8
  import { lintPlanStage } from "./artifact-linter/plan.js";
@@ -48,7 +47,16 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
48
47
  }
49
48
  const raw = await fs.readFile(absFile, "utf8");
50
49
  const sections = extractH2Sections(raw);
51
- const projectConfig = await readConfig(projectRoot);
50
+ const duplicateHeadings = duplicateH2Headings(raw);
51
+ if (duplicateHeadings.length > 0) {
52
+ findings.push({
53
+ section: "duplicate_h2_heading",
54
+ required: false,
55
+ rule: "[P3] keep each `##` heading unique within an artifact; append updates to the existing section instead of cloning headings.",
56
+ found: false,
57
+ details: `Duplicate H2 heading(s): ${duplicateHeadings.join(", ")}. Merge edits into the existing heading to avoid split contracts.`
58
+ });
59
+ }
52
60
  const parsedFrontmatter = parseFrontmatter(raw);
53
61
  const frontmatterMissingKeys = FRONTMATTER_REQUIRED_KEYS.filter((key) => {
54
62
  const value = parsedFrontmatter.values[key];
@@ -87,8 +95,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
87
95
  });
88
96
  const brainstormShortCircuitBody = stage === "brainstorm" ? sectionBodyByName(sections, "Short-Circuit Decision") : null;
89
97
  const brainstormShortCircuitActivated = stage === "brainstorm" && isShortCircuitActivated(brainstormShortCircuitBody);
90
- const scopePreAuditEnabled = projectConfig.optInAudits?.scopePreAudit === true;
91
- const staleDiagramAuditEnabled = projectConfig.optInAudits?.staleDiagramAudit === true;
98
+ const scopePreAuditEnabled = true;
99
+ const staleDiagramAuditEnabled = true;
92
100
  const isTrivialOverride = Boolean(schema.trivialOverrideSections &&
93
101
  schema.trivialOverrideSections.length > 0 &&
94
102
  (/trivial.change|mini.design|escape.hatch/iu.test(raw) ||
package/dist/cli.js CHANGED
@@ -425,11 +425,8 @@ async function runCommand(parsed, ctx) {
425
425
  info(ctx, `Detected harnesses from repo: ${resolved.detectedHarnesses.join(", ")}`);
426
426
  }
427
427
  ctx.stdout.write(`${JSON.stringify({
428
- track: previewConfig.defaultTrack ?? "standard",
428
+ track: effectiveTrack ?? "standard",
429
429
  harnesses: previewConfig.harnesses,
430
- strictness: previewConfig.strictness ?? "advisory",
431
- gitHookGuards: previewConfig.gitHookGuards,
432
- languageRulePacks: previewConfig.languageRulePacks,
433
430
  generatedSurfaces: previewSurfaces
434
431
  }, null, 2)}\n`);
435
432
  return 0;
@@ -444,11 +441,7 @@ async function runCommand(parsed, ctx) {
444
441
  }
445
442
  const trackNote = effectiveTrack ? ` (track=${effectiveTrack})` : "";
446
443
  info(ctx, `Initialized .cclaw runtime and generated harness shims${trackNote}`);
447
- // Point new users at the one config surface they might actually flip —
448
- // `strictness` and `gitHookGuards` — without overselling the other knobs
449
- // (those live behind docs/config.md until someone needs them).
450
- info(ctx, "Config: .cclaw/config.yaml (strictness=advisory, gitHookGuards=false).");
451
- info(ctx, "Need stricter guards or language rule packs? See docs/config.md.");
444
+ info(ctx, "Config: .cclaw/config.yaml (harnesses + auto-managed version stamps).");
452
445
  await maybeEnableCodexHooksFlag(effectiveHarnesses, parsed, ctx);
453
446
  return 0;
454
447
  }
package/dist/config.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack } from "./types.js";
2
+ export declare const DEFAULT_TDD_TEST_PATH_PATTERNS: readonly string[];
3
+ export declare const DEFAULT_TDD_TEST_GLOBS: readonly string[];
4
+ export declare const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS: readonly string[];
5
+ export declare const DEFAULT_COMPOUND_RECURRENCE_THRESHOLD = 3;
6
+ export declare const DEFAULT_EARLY_LOOP_MAX_ITERATIONS = 3;
2
7
  export interface ConfigWarningState {
3
8
  emitted: Set<string>;
4
9
  }
@@ -10,73 +15,12 @@ export declare class InvalidConfigError extends Error {
10
15
  constructor(message: string);
11
16
  }
12
17
  export declare function configPath(projectRoot: string): string;
13
- /**
14
- * Default test-path patterns used by the workflow-guard hook to classify TDD writes.
15
- *
16
- * Scope is intentionally narrow and language-agnostic; users can extend this
17
- * list in config when their repository uses different conventions.
18
- */
19
- export declare const DEFAULT_TDD_TEST_PATH_PATTERNS: readonly string[];
20
- /**
21
- * Legacy alias kept for backwards compatibility with `tddTestGlobs`.
22
- * Prefer `tdd.testPathPatterns` in new configurations.
23
- */
24
- export declare const DEFAULT_TDD_TEST_GLOBS: readonly string[];
25
- export declare const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS: readonly string[];
26
- export declare const DEFAULT_COMPOUND_RECURRENCE_THRESHOLD = 3;
27
- export declare const DEFAULT_EARLY_LOOP_MAX_ITERATIONS = 3;
28
- /**
29
- * Populated runtime view of config values that downstream callers (install,
30
- * observe, sync/runtime checks) consume. Always has the derived guard modes populated,
31
- * regardless of whether the user wrote `strictness`, the legacy keys, both,
32
- * or neither.
33
- */
34
- export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): CclawConfig;
35
- /**
36
- * Probe common project-root manifests to infer which language rule packs the
37
- * user would reasonably want. Pure-functional best-effort: any filesystem
38
- * error is swallowed, producing an empty list — the user can always override
39
- * by hand.
40
- *
41
- * Called from `cclaw init` only (not `readConfig`), so subsequent upgrades
42
- * never surprise a user who intentionally cleared the list.
43
- */
44
- export declare function detectLanguageRulePacks(projectRoot: string): Promise<LanguageRulePack[]>;
45
- export declare function readConfig(projectRoot: string, options?: ReadConfigOptions): Promise<CclawConfig>;
46
- /**
47
- * Fields that live on the populated runtime `CclawConfig` but are considered
48
- * "advanced" — we keep them in the in-memory object so downstream callers
49
- * don't have to branch, but we do **not** write them to `config.yaml` unless
50
- * the user set them explicitly. Keeps the default template small and honest:
51
- * only knobs a new user would meaningfully flip show up.
52
- */
53
- type AdvancedConfigKey = "vcs" | "tddTestGlobs" | "tdd" | "compound" | "earlyLoop" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview" | "ironLaws" | "optInAudits" | "reviewLoop";
54
- /**
55
- * Options controlling the serialisation shape of `config.yaml`.
56
- *
57
- * - `"full"` (default): write every field on the `CclawConfig` object that
58
- * isn't `undefined`. Preserves existing shapes and keeps legacy callers
59
- * working without migration.
60
- * - `"minimal"`: write only the user-facing knobs (`MINIMAL_CONFIG_KEYS`)
61
- * plus any non-empty `languageRulePacks` (so auto-detected values survive
62
- * a fresh `cclaw init`). Use this when generating the default template;
63
- * power users can still add advanced keys by hand.
64
- *
65
- * `advancedKeysPresent` upgrades an otherwise-minimal serialisation by
66
- * including the listed advanced keys. `cclaw upgrade` uses it to preserve
67
- * the exact shape a user hand-authored, while still re-minimising configs
68
- * where the user stayed at defaults.
69
- */
18
+ export declare function createDefaultConfig(harnesses?: HarnessId[], _defaultTrack?: FlowTrack): CclawConfig;
19
+ export declare function detectLanguageRulePacks(_projectRoot: string): Promise<LanguageRulePack[]>;
20
+ export declare function readConfig(projectRoot: string, _options?: ReadConfigOptions): Promise<CclawConfig>;
70
21
  export interface WriteConfigOptions {
71
22
  mode?: "full" | "minimal";
72
- advancedKeysPresent?: ReadonlySet<AdvancedConfigKey>;
23
+ advancedKeysPresent?: ReadonlySet<never>;
73
24
  }
74
- export declare function writeConfig(projectRoot: string, config: CclawConfig, options?: WriteConfigOptions): Promise<void>;
75
- /**
76
- * Enumerate which advanced keys are currently set in the on-disk config.
77
- * Used by `cclaw upgrade` to preserve the user's existing shape — if they
78
- * wrote `tddTestGlobs` by hand, the upgrade keeps it; if they didn't, the
79
- * upgrade stays minimal.
80
- */
81
- export declare function detectAdvancedKeys(projectRoot: string): Promise<ReadonlySet<AdvancedConfigKey>>;
82
- export {};
25
+ export declare function writeConfig(projectRoot: string, config: CclawConfig, _options?: WriteConfigOptions): Promise<void>;
26
+ export declare function detectAdvancedKeys(_projectRoot: string): Promise<ReadonlySet<never>>;