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.
- package/dist/artifact-linter/brainstorm.js +15 -1
- package/dist/artifact-linter/design.js +14 -0
- package/dist/artifact-linter/scope.js +14 -0
- package/dist/artifact-linter/shared.d.ts +1 -0
- package/dist/artifact-linter/shared.js +32 -0
- package/dist/artifact-linter.js +13 -5
- package/dist/cli.js +2 -9
- package/dist/config.d.ts +11 -67
- package/dist/config.js +59 -649
- package/dist/content/hook-events.js +1 -5
- package/dist/content/hook-manifest.d.ts +6 -4
- package/dist/content/hook-manifest.js +16 -65
- package/dist/content/hooks.js +54 -14
- package/dist/content/meta-skill.js +4 -3
- package/dist/content/node-hooks.d.ts +0 -26
- package/dist/content/node-hooks.js +459 -157
- package/dist/content/observe.js +5 -4
- package/dist/content/opencode-plugin.js +1 -78
- package/dist/content/skills-elicitation.d.ts +1 -0
- package/dist/content/skills-elicitation.js +123 -0
- package/dist/content/skills.js +6 -4
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +6 -2
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/scope.js +9 -5
- package/dist/content/stages/tdd.js +11 -11
- package/dist/content/start-command.js +4 -4
- package/dist/content/templates.js +21 -0
- package/dist/flow-state.d.ts +7 -0
- package/dist/flow-state.js +1 -0
- package/dist/gate-evidence.js +1 -5
- package/dist/hook-schema.js +3 -0
- package/dist/hook-schemas/claude-hooks.v1.json +2 -5
- package/dist/hook-schemas/codex-hooks.v1.json +1 -4
- package/dist/hook-schemas/cursor-hooks.v1.json +1 -3
- package/dist/install.d.ts +2 -7
- package/dist/install.js +32 -123
- package/dist/internal/advance-stage/advance.js +22 -1
- package/dist/internal/advance-stage/parsers.d.ts +1 -0
- package/dist/internal/advance-stage/parsers.js +6 -0
- package/dist/internal/compound-readiness.js +1 -16
- package/dist/internal/early-loop-status.js +1 -3
- package/dist/internal/runtime-integrity.js +0 -20
- package/dist/policy.js +6 -9
- package/dist/run-persistence.d.ts +1 -1
- package/dist/run-persistence.js +29 -2
- package/dist/runtime/run-hook.mjs +459 -265
- package/dist/tdd-verification-evidence.js +6 -18
- package/dist/track-heuristics.d.ts +7 -1
- package/dist/track-heuristics.js +12 -0
- package/dist/types.d.ts +0 -56
- 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()) {
|
package/dist/artifact-linter.js
CHANGED
|
@@ -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
|
|
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 =
|
|
91
|
-
const staleDiagramAuditEnabled =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
23
|
+
advancedKeysPresent?: ReadonlySet<never>;
|
|
73
24
|
}
|
|
74
|
-
export declare function writeConfig(projectRoot: string, config: CclawConfig,
|
|
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>>;
|