cclaw-cli 0.51.15 → 0.51.16
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.js
CHANGED
|
@@ -604,11 +604,15 @@ function validateApproachesTaxonomy(sectionBody) {
|
|
|
604
604
|
const roleIndex = columnIndex(header, "role");
|
|
605
605
|
const upsideIndex = columnIndex(header, "upside");
|
|
606
606
|
if (roleIndex < 0 || upsideIndex < 0) {
|
|
607
|
+
const firstColumnTokens = rows.map((row) => normalizeTableToken(row[0] ?? ""));
|
|
608
|
+
const appearsTransposed = firstColumnTokens.includes("role") || firstColumnTokens.includes("upside");
|
|
607
609
|
return {
|
|
608
610
|
rowCount: rows.length,
|
|
609
611
|
roleUpsideOk: false,
|
|
610
612
|
challengerOk: false,
|
|
611
|
-
details:
|
|
613
|
+
details: appearsTransposed
|
|
614
|
+
? "Approaches table appears transposed: `Role`/`Upside` are rows, but must be columns. Use `| Approach | Role | Upside | ... |` with one approach per row."
|
|
615
|
+
: "Approaches table must include canonical `Role` and `Upside` columns (Role: baseline | challenger | wild-card; Upside: low | modest | high | higher)."
|
|
612
616
|
};
|
|
613
617
|
}
|
|
614
618
|
let challengerRows = 0;
|
|
@@ -649,6 +653,21 @@ function validateApproachesTaxonomy(sectionBody) {
|
|
|
649
653
|
: `Approaches table must include exactly one challenger row with Upside high or higher. Found ${challengerRows} challenger row(s).`
|
|
650
654
|
};
|
|
651
655
|
}
|
|
656
|
+
function validateCalibratedSelfReview(sectionBody) {
|
|
657
|
+
const hasStatus = /^\s*-\s*Status:\s*(?:Approved|Issues Found)\s*$/imu.test(sectionBody);
|
|
658
|
+
const hasPatches = /^\s*-\s*Patches applied:\s*$/imu.test(sectionBody);
|
|
659
|
+
const hasConcerns = /^\s*-\s*Remaining concerns:\s*$/imu.test(sectionBody);
|
|
660
|
+
if (!hasStatus || !hasPatches || !hasConcerns) {
|
|
661
|
+
return {
|
|
662
|
+
ok: false,
|
|
663
|
+
details: "Self-Review Notes must use the calibrated review prompt format: `- Status: Approved | Issues Found`, `- Patches applied:`, and `- Remaining concerns:`."
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
ok: true,
|
|
668
|
+
details: "Self-Review Notes use the calibrated review prompt format."
|
|
669
|
+
};
|
|
670
|
+
}
|
|
652
671
|
function validateRequirementsTaxonomy(sectionBody) {
|
|
653
672
|
const header = tableHeaderCells(sectionBody);
|
|
654
673
|
if (!header) {
|
|
@@ -1786,6 +1805,17 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
|
|
|
1786
1805
|
});
|
|
1787
1806
|
}
|
|
1788
1807
|
}
|
|
1808
|
+
const selfReviewBody = sectionBodyByName(sections, "Self-Review Notes");
|
|
1809
|
+
if (selfReviewBody !== null) {
|
|
1810
|
+
const selfReview = validateCalibratedSelfReview(selfReviewBody);
|
|
1811
|
+
findings.push({
|
|
1812
|
+
section: "Calibrated Self-Review Format",
|
|
1813
|
+
required: true,
|
|
1814
|
+
rule: "When Self-Review Notes are present, they must use the calibrated review prompt output shape.",
|
|
1815
|
+
found: selfReview.ok,
|
|
1816
|
+
details: selfReview.details
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1789
1819
|
}
|
|
1790
1820
|
if (stage === "design") {
|
|
1791
1821
|
const tierResolution = await resolveDesignDiagramTier(projectRoot, track, raw);
|
|
@@ -879,6 +879,41 @@ async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
|
|
|
879
879
|
};
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
+
async function readStageSupportContext(root, currentStage) {
|
|
883
|
+
const stage = typeof currentStage === "string" ? currentStage : "";
|
|
884
|
+
const validStages = new Set(["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"]);
|
|
885
|
+
if (!validStages.has(stage)) return [];
|
|
886
|
+
|
|
887
|
+
const parts = [];
|
|
888
|
+
const contractPath = path.join(root, RUNTIME_ROOT, "templates", "state-contracts", stage + ".json");
|
|
889
|
+
const contract = (await readTextFile(contractPath, "")).trim();
|
|
890
|
+
if (contract.length > 0) {
|
|
891
|
+
parts.push(
|
|
892
|
+
"Current stage state contract (read before drafting or editing the stage artifact):\\n" +
|
|
893
|
+
contract
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const reviewPromptByStage = {
|
|
898
|
+
brainstorm: "brainstorm-self-review.md",
|
|
899
|
+
scope: "scope-ceo-review.md",
|
|
900
|
+
design: "design-eng-review.md"
|
|
901
|
+
};
|
|
902
|
+
const promptName = reviewPromptByStage[stage];
|
|
903
|
+
if (typeof promptName === "string") {
|
|
904
|
+
const promptPath = path.join(root, RUNTIME_ROOT, "skills", "review-prompts", promptName);
|
|
905
|
+
const prompt = (await readTextFile(promptPath, "")).trim();
|
|
906
|
+
if (prompt.length > 0) {
|
|
907
|
+
parts.push(
|
|
908
|
+
"Current stage calibrated review prompt (use before asking for approval/completion):\\n" +
|
|
909
|
+
prompt
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return parts;
|
|
915
|
+
}
|
|
916
|
+
|
|
882
917
|
async function handleSessionStart(runtime) {
|
|
883
918
|
const state = await readFlowState(runtime.root);
|
|
884
919
|
const stateDir = path.join(runtime.root, RUNTIME_ROOT, "state");
|
|
@@ -984,6 +1019,7 @@ async function handleSessionStart(runtime) {
|
|
|
984
1019
|
const staleStages = toObject(state.raw.staleStages) || {};
|
|
985
1020
|
const staleStageNames = Object.keys(staleStages);
|
|
986
1021
|
const metaContent = (await readTextFile(metaSkillFile, "")).trim();
|
|
1022
|
+
const stageSupportContext = await readStageSupportContext(runtime.root, state.currentStage);
|
|
987
1023
|
|
|
988
1024
|
const parts = [
|
|
989
1025
|
"cclaw loaded. Flow: stage=" +
|
|
@@ -1017,6 +1053,9 @@ async function handleSessionStart(runtime) {
|
|
|
1017
1053
|
knowledge.digestLines.join("\\n")
|
|
1018
1054
|
);
|
|
1019
1055
|
}
|
|
1056
|
+
if (stageSupportContext.length > 0) {
|
|
1057
|
+
parts.push(...stageSupportContext);
|
|
1058
|
+
}
|
|
1020
1059
|
if (ironLawLines.length > 0) {
|
|
1021
1060
|
parts.push("Iron laws (enforced policy highlights):\\n" + ironLawLines.join("\\n"));
|
|
1022
1061
|
}
|
|
@@ -4,7 +4,7 @@ export function opencodePluginJs(_options = {}) {
|
|
|
4
4
|
return `// cclaw OpenCode plugin — generated by npx cclaw-cli sync
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { readFile, stat } from "node:fs/promises";
|
|
7
|
-
import { join } from "node:path";
|
|
7
|
+
import { basename, join } from "node:path";
|
|
8
8
|
|
|
9
9
|
export default function cclawPlugin(ctx) {
|
|
10
10
|
const root = ctx.directory || process.cwd();
|
|
@@ -16,6 +16,13 @@ export default function cclawPlugin(ctx) {
|
|
|
16
16
|
const flowStatePath = join(stateDir, "flow-state.json");
|
|
17
17
|
const knowledgePath = join(runtimeDir, "knowledge.jsonl");
|
|
18
18
|
const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
|
|
19
|
+
const STAGE_IDS = ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"];
|
|
20
|
+
const REVIEW_PROMPT_BY_STAGE = {
|
|
21
|
+
brainstorm: "brainstorm-self-review.md",
|
|
22
|
+
scope: "scope-ceo-review.md",
|
|
23
|
+
design: "design-eng-review.md"
|
|
24
|
+
};
|
|
25
|
+
const REVIEW_PROMPT_FILES = Object.values(REVIEW_PROMPT_BY_STAGE);
|
|
19
26
|
|
|
20
27
|
function ensureRuntimeDirs() {
|
|
21
28
|
try {
|
|
@@ -83,6 +90,29 @@ export default function cclawPlugin(ctx) {
|
|
|
83
90
|
return readTailLines(knowledgePath, 12);
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
async function readStageSupportContext(stage) {
|
|
94
|
+
if (typeof stage !== "string" || !STAGE_IDS.includes(stage)) return [];
|
|
95
|
+
const parts = [];
|
|
96
|
+
const contract = (await readFileText(join(runtimeDir, "templates/state-contracts", stage + ".json"))).trim();
|
|
97
|
+
if (contract.length > 0) {
|
|
98
|
+
parts.push(
|
|
99
|
+
"Current stage state contract (read before drafting or editing the stage artifact):\\n" +
|
|
100
|
+
contract
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const reviewPromptName = REVIEW_PROMPT_BY_STAGE[stage];
|
|
104
|
+
if (reviewPromptName) {
|
|
105
|
+
const prompt = (await readFileText(join(runtimeDir, "skills/review-prompts", reviewPromptName))).trim();
|
|
106
|
+
if (prompt.length > 0) {
|
|
107
|
+
parts.push(
|
|
108
|
+
"Current stage calibrated review prompt (use before asking for approval/completion):\\n" +
|
|
109
|
+
prompt
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return parts;
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
const BOOTSTRAP_MARKER = "<!-- cclaw-bootstrap-v1 -->";
|
|
87
117
|
|
|
88
118
|
async function buildBootstrap() {
|
|
@@ -97,6 +127,9 @@ export default function cclawPlugin(ctx) {
|
|
|
97
127
|
const knowledge = await readKnowledgeDigest();
|
|
98
128
|
if (knowledge.length > 0) parts.push("Knowledge digest (top relevant entries):", ...knowledge);
|
|
99
129
|
|
|
130
|
+
const stageSupport = await readStageSupportContext(flow.stage);
|
|
131
|
+
if (stageSupport.length > 0) parts.push(...stageSupport);
|
|
132
|
+
|
|
100
133
|
parts.push(
|
|
101
134
|
"If you discover a non-obvious rule or pattern during stage work, add it to the current artifact ## Learnings section; stage-complete harvests it into .cclaw/knowledge.jsonl. Direct JSONL append is only for explicit manual learnings operations."
|
|
102
135
|
);
|
|
@@ -112,7 +145,9 @@ export default function cclawPlugin(ctx) {
|
|
|
112
145
|
const BOOTSTRAP_SOURCE_PATHS = [
|
|
113
146
|
flowStatePath,
|
|
114
147
|
knowledgePath,
|
|
115
|
-
metaSkillPath
|
|
148
|
+
metaSkillPath,
|
|
149
|
+
...STAGE_IDS.map((stage) => join(runtimeDir, "templates/state-contracts", stage + ".json")),
|
|
150
|
+
...REVIEW_PROMPT_FILES.map((file) => join(runtimeDir, "skills/review-prompts", file))
|
|
116
151
|
];
|
|
117
152
|
|
|
118
153
|
async function readMtimeMs(filePath) {
|
|
@@ -244,6 +279,23 @@ export default function cclawPlugin(ctx) {
|
|
|
244
279
|
return false;
|
|
245
280
|
}
|
|
246
281
|
|
|
282
|
+
function resolveNodeExecutable() {
|
|
283
|
+
const override = typeof process.env.CCLAW_NODE_EXECUTABLE === "string"
|
|
284
|
+
? process.env.CCLAW_NODE_EXECUTABLE.trim()
|
|
285
|
+
: "";
|
|
286
|
+
if (override.length > 0) return override;
|
|
287
|
+
|
|
288
|
+
const execName = basename(process.execPath || "").toLowerCase();
|
|
289
|
+
if (execName === "node" || execName === "node.exe") {
|
|
290
|
+
return process.execPath;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// OpenCode can host plugins from its own CLI binary, making
|
|
294
|
+
// process.execPath point at opencode instead of Node. Fall back to the
|
|
295
|
+
// user's Node on PATH so generated cclaw hooks execute as JavaScript.
|
|
296
|
+
return "node";
|
|
297
|
+
}
|
|
298
|
+
|
|
247
299
|
async function runHookScript(hookName, payload = {}) {
|
|
248
300
|
const { spawn } = await import("node:child_process");
|
|
249
301
|
const hookRuntimePath = join(root, "${RUNTIME_ROOT}/hooks/run-hook.mjs");
|
|
@@ -260,7 +312,7 @@ export default function cclawPlugin(ctx) {
|
|
|
260
312
|
|
|
261
313
|
let child;
|
|
262
314
|
try {
|
|
263
|
-
child = spawn(
|
|
315
|
+
child = spawn(resolveNodeExecutable(), [hookRuntimePath, hookName], {
|
|
264
316
|
cwd: root,
|
|
265
317
|
stdio: ["pipe", "ignore", "pipe"]
|
|
266
318
|
});
|
|
@@ -96,7 +96,11 @@ ${SEED_SHELF_SECTION}
|
|
|
96
96
|
- (compact ASCII/Mermaid diagram for medium+ complexity, or one-line justification for omission.)
|
|
97
97
|
|
|
98
98
|
## Self-Review Notes
|
|
99
|
-
-
|
|
99
|
+
- Status: Approved | Issues Found
|
|
100
|
+
- Patches applied:
|
|
101
|
+
- None
|
|
102
|
+
- Remaining concerns:
|
|
103
|
+
- None
|
|
100
104
|
|
|
101
105
|
## Assumptions and Open Questions
|
|
102
106
|
- **Assumptions:**
|
|
@@ -4,8 +4,8 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { resolveArtifactPath } from "../artifact-paths.js";
|
|
6
6
|
import { RUNTIME_ROOT, SHIP_FINALIZATION_MODES } from "../constants.js";
|
|
7
|
-
import { stageSchema } from "../content/stage-schema.js";
|
|
8
|
-
import { appendDelegation, checkMandatoryDelegations } from "../delegation.js";
|
|
7
|
+
import { stageAutoSubagentDispatch, stageSchema } from "../content/stage-schema.js";
|
|
8
|
+
import { appendDelegation, checkMandatoryDelegations, readDelegationLedger } from "../delegation.js";
|
|
9
9
|
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../gate-evidence.js";
|
|
10
10
|
import { extractMarkdownSectionBody, parseLearningsSection } from "../artifact-linter.js";
|
|
11
11
|
import { getAvailableTransitions, getTransitionGuards, isFlowTrack, createInitialFlowState } from "../flow-state.js";
|
|
@@ -827,6 +827,7 @@ async function runAdvanceStage(projectRoot, args, io) {
|
|
|
827
827
|
nextGuardEvidence[gateId] = provided.trim();
|
|
828
828
|
}
|
|
829
829
|
}
|
|
830
|
+
await ensureProactiveDelegationTrace(projectRoot, args.stage);
|
|
830
831
|
const nextStageCatalog = {
|
|
831
832
|
required: [...catalog.required],
|
|
832
833
|
recommended: [...catalog.recommended],
|
|
@@ -968,6 +969,121 @@ function firstIncompleteStageForTrack(track, completedStages) {
|
|
|
968
969
|
const stages = TRACK_STAGES[track];
|
|
969
970
|
return stages.find((stage) => !completed.has(stage)) ?? stages[stages.length - 1] ?? "brainstorm";
|
|
970
971
|
}
|
|
972
|
+
async function ensureProactiveDelegationTrace(projectRoot, stage) {
|
|
973
|
+
const proactiveRules = stageAutoSubagentDispatch(stage).filter((rule) => rule.mode === "proactive");
|
|
974
|
+
if (proactiveRules.length === 0)
|
|
975
|
+
return;
|
|
976
|
+
const ledger = await readDelegationLedger(projectRoot);
|
|
977
|
+
const currentRunEntries = ledger.entries.filter((entry) => entry.runId === ledger.runId);
|
|
978
|
+
for (const rule of proactiveRules) {
|
|
979
|
+
const alreadyRecorded = currentRunEntries.some((entry) => entry.stage === stage && entry.agent === rule.agent && entry.mode === "proactive");
|
|
980
|
+
if (alreadyRecorded)
|
|
981
|
+
continue;
|
|
982
|
+
await appendDelegation(projectRoot, {
|
|
983
|
+
stage,
|
|
984
|
+
agent: rule.agent,
|
|
985
|
+
mode: "proactive",
|
|
986
|
+
status: "waived",
|
|
987
|
+
waiverReason: "auto-recorded: proactive delegation was not explicitly triggered before stage completion",
|
|
988
|
+
conditionTrigger: rule.when,
|
|
989
|
+
skill: rule.skill,
|
|
990
|
+
ts: new Date().toISOString()
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
async function pathExists(projectRoot, relPath) {
|
|
995
|
+
try {
|
|
996
|
+
await fs.stat(path.join(projectRoot, relPath));
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
catch {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
async function listExistingFiles(projectRoot, relPaths) {
|
|
1004
|
+
const matches = [];
|
|
1005
|
+
for (const relPath of relPaths) {
|
|
1006
|
+
try {
|
|
1007
|
+
const stat = await fs.stat(path.join(projectRoot, relPath));
|
|
1008
|
+
if (stat.isFile())
|
|
1009
|
+
matches.push(relPath);
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// continue
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return matches;
|
|
1016
|
+
}
|
|
1017
|
+
async function listFilesUnder(projectRoot, relDir, limit = 20) {
|
|
1018
|
+
const root = path.join(projectRoot, relDir);
|
|
1019
|
+
const out = [];
|
|
1020
|
+
async function walk(absDir) {
|
|
1021
|
+
if (out.length >= limit)
|
|
1022
|
+
return;
|
|
1023
|
+
let entries;
|
|
1024
|
+
try {
|
|
1025
|
+
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
1026
|
+
}
|
|
1027
|
+
catch {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
for (const entry of entries) {
|
|
1031
|
+
if (out.length >= limit)
|
|
1032
|
+
return;
|
|
1033
|
+
if (entry.name.startsWith("."))
|
|
1034
|
+
continue;
|
|
1035
|
+
const abs = path.join(absDir, entry.name);
|
|
1036
|
+
if (entry.isDirectory()) {
|
|
1037
|
+
await walk(abs);
|
|
1038
|
+
}
|
|
1039
|
+
else if (entry.isFile()) {
|
|
1040
|
+
out.push(path.relative(projectRoot, abs).split(path.sep).join("/"));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
await walk(root);
|
|
1045
|
+
return out;
|
|
1046
|
+
}
|
|
1047
|
+
async function discoverStartFlowContext(projectRoot) {
|
|
1048
|
+
const lines = [];
|
|
1049
|
+
const seedFiles = (await listFilesUnder(projectRoot, path.join(RUNTIME_ROOT, "seeds"), 10))
|
|
1050
|
+
.filter((relPath) => /^\.cclaw\/seeds\/SEED-.*\.md$/u.test(relPath));
|
|
1051
|
+
lines.push(seedFiles.length > 0
|
|
1052
|
+
? `- Seed shelf scanned: ${seedFiles.join(", ")}.`
|
|
1053
|
+
: "- Seed shelf scanned: no `.cclaw/seeds/SEED-*.md` files found.");
|
|
1054
|
+
const originDirs = ["docs/prd", "docs/rfcs", "docs/adr", "docs/design", "specs", "prd", "rfc", "design"];
|
|
1055
|
+
const originRootFiles = ["PRD.md", "SPEC.md", "DESIGN.md", "REQUIREMENTS.md", "ROADMAP.md"];
|
|
1056
|
+
const originFiles = [
|
|
1057
|
+
...(await listExistingFiles(projectRoot, originRootFiles)),
|
|
1058
|
+
...(await Promise.all(originDirs.map((dir) => listFilesUnder(projectRoot, dir, 6)))).flat()
|
|
1059
|
+
].slice(0, 20);
|
|
1060
|
+
lines.push(originFiles.length > 0
|
|
1061
|
+
? `- Origin docs scanned: found ${originFiles.join(", ")}.`
|
|
1062
|
+
: "- Origin docs scanned: no PRD/RFC/ADR/design/spec files found in configured locations.");
|
|
1063
|
+
const stackMarkers = await listExistingFiles(projectRoot, [
|
|
1064
|
+
"package.json",
|
|
1065
|
+
"pyproject.toml",
|
|
1066
|
+
"requirements.txt",
|
|
1067
|
+
"requirements-dev.txt",
|
|
1068
|
+
".python-version",
|
|
1069
|
+
"go.mod",
|
|
1070
|
+
"Cargo.toml",
|
|
1071
|
+
"pom.xml",
|
|
1072
|
+
"build.gradle",
|
|
1073
|
+
"build.gradle.kts",
|
|
1074
|
+
"Dockerfile",
|
|
1075
|
+
"docker-compose.yml",
|
|
1076
|
+
"docker-compose.yaml",
|
|
1077
|
+
".gitlab-ci.yml"
|
|
1078
|
+
]);
|
|
1079
|
+
if (await pathExists(projectRoot, ".github/workflows")) {
|
|
1080
|
+
stackMarkers.push(".github/workflows/");
|
|
1081
|
+
}
|
|
1082
|
+
lines.push(stackMarkers.length > 0
|
|
1083
|
+
? `- Stack markers scanned: found ${stackMarkers.join(", ")}.`
|
|
1084
|
+
: "- Stack markers scanned: no root stack markers found.");
|
|
1085
|
+
return lines;
|
|
1086
|
+
}
|
|
971
1087
|
async function appendIdeaArtifact(projectRoot, args, previous) {
|
|
972
1088
|
const artifactPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "00-idea.md");
|
|
973
1089
|
await fs.mkdir(path.dirname(artifactPath), { recursive: true });
|
|
@@ -984,6 +1100,7 @@ async function appendIdeaArtifact(projectRoot, args, previous) {
|
|
|
984
1100
|
await fs.appendFile(artifactPath, entry, "utf8");
|
|
985
1101
|
return;
|
|
986
1102
|
}
|
|
1103
|
+
const discoveredContext = await discoverStartFlowContext(projectRoot);
|
|
987
1104
|
const body = [
|
|
988
1105
|
"# Idea",
|
|
989
1106
|
`Class: ${args.className || "unspecified"}`,
|
|
@@ -994,7 +1111,7 @@ async function appendIdeaArtifact(projectRoot, args, previous) {
|
|
|
994
1111
|
args.prompt || "(not provided)",
|
|
995
1112
|
"",
|
|
996
1113
|
"## Discovered context",
|
|
997
|
-
|
|
1114
|
+
...discoveredContext
|
|
998
1115
|
].join("\n") + "\n";
|
|
999
1116
|
await fs.writeFile(artifactPath, body, "utf8");
|
|
1000
1117
|
}
|