cclaw-cli 0.43.0 → 0.45.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.
@@ -0,0 +1,525 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { RUNTIME_ROOT } from "../constants.js";
4
+ import { stageSchema } from "../content/stage-schema.js";
5
+ import { appendDelegation, checkMandatoryDelegations } from "../delegation.js";
6
+ import { readActiveFeature } from "../feature-system.js";
7
+ import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../gate-evidence.js";
8
+ import { extractMarkdownSectionBody, parseLearningsSection } from "../artifact-linter.js";
9
+ import { isFlowTrack, nextStage } from "../flow-state.js";
10
+ import { appendKnowledge } from "../knowledge-store.js";
11
+ import { readFlowState, writeFlowState } from "../runs.js";
12
+ import { FLOW_STAGES } from "../types.js";
13
+ function unique(values) {
14
+ return [...new Set(values)];
15
+ }
16
+ function parseStringList(raw) {
17
+ if (!Array.isArray(raw))
18
+ return [];
19
+ return raw
20
+ .filter((item) => typeof item === "string")
21
+ .map((item) => item.trim())
22
+ .filter((item) => item.length > 0);
23
+ }
24
+ function isFlowStageValue(value) {
25
+ return typeof value === "string" && FLOW_STAGES.includes(value);
26
+ }
27
+ function parseGuardEvidence(value) {
28
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
29
+ return {};
30
+ }
31
+ const next = {};
32
+ for (const [key, raw] of Object.entries(value)) {
33
+ if (typeof raw !== "string")
34
+ continue;
35
+ const trimmed = raw.trim();
36
+ if (trimmed.length === 0)
37
+ continue;
38
+ next[key] = trimmed;
39
+ }
40
+ return next;
41
+ }
42
+ function parseCandidateGateCatalog(value, fallback) {
43
+ const next = {};
44
+ for (const stage of FLOW_STAGES) {
45
+ const base = fallback[stage];
46
+ next[stage] = {
47
+ required: [...base.required],
48
+ recommended: [...base.recommended],
49
+ conditional: [...base.conditional],
50
+ triggered: [...base.triggered],
51
+ passed: [...base.passed],
52
+ blocked: [...base.blocked]
53
+ };
54
+ }
55
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
56
+ return next;
57
+ }
58
+ const rawCatalog = value;
59
+ for (const stage of FLOW_STAGES) {
60
+ const rawStage = rawCatalog[stage];
61
+ if (!rawStage || typeof rawStage !== "object" || Array.isArray(rawStage)) {
62
+ continue;
63
+ }
64
+ const typed = rawStage;
65
+ const base = fallback[stage];
66
+ const allowed = new Set([...base.required, ...base.recommended, ...base.conditional]);
67
+ const conditional = new Set(base.conditional);
68
+ const passed = unique(parseStringList(typed.passed)).filter((gateId) => allowed.has(gateId));
69
+ const blocked = unique(parseStringList(typed.blocked)).filter((gateId) => allowed.has(gateId));
70
+ const triggered = unique([
71
+ ...parseStringList(typed.triggered).filter((gateId) => conditional.has(gateId)),
72
+ ...passed.filter((gateId) => conditional.has(gateId)),
73
+ ...blocked.filter((gateId) => conditional.has(gateId))
74
+ ]);
75
+ next[stage] = {
76
+ required: [...base.required],
77
+ recommended: [...base.recommended],
78
+ conditional: [...base.conditional],
79
+ triggered,
80
+ passed,
81
+ blocked
82
+ };
83
+ }
84
+ return next;
85
+ }
86
+ function coerceCandidateFlowState(raw, fallback) {
87
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
88
+ return fallback;
89
+ }
90
+ const typed = raw;
91
+ const track = isFlowTrack(typed.track) ? typed.track : fallback.track;
92
+ const currentStage = isFlowStageValue(typed.currentStage)
93
+ ? typed.currentStage
94
+ : fallback.currentStage;
95
+ const completedStages = unique(parseStringList(typed.completedStages).filter((stage) => isFlowStageValue(stage)));
96
+ const skippedStagesRaw = parseStringList(typed.skippedStages).filter((stage) => isFlowStageValue(stage));
97
+ const skippedStages = skippedStagesRaw.length > 0 ? skippedStagesRaw : fallback.skippedStages;
98
+ return {
99
+ ...fallback,
100
+ currentStage,
101
+ completedStages,
102
+ track,
103
+ skippedStages,
104
+ guardEvidence: parseGuardEvidence(typed.guardEvidence),
105
+ stageGateCatalog: parseCandidateGateCatalog(typed.stageGateCatalog, fallback.stageGateCatalog)
106
+ };
107
+ }
108
+ function parseEvidenceByGate(raw) {
109
+ if (!raw || raw.trim().length === 0) {
110
+ return {};
111
+ }
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(raw);
115
+ }
116
+ catch (err) {
117
+ throw new Error(`--evidence-json must be valid JSON object: ${err instanceof Error ? err.message : String(err)}`);
118
+ }
119
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
120
+ throw new Error("--evidence-json must deserialize to an object.");
121
+ }
122
+ const next = {};
123
+ for (const [key, value] of Object.entries(parsed)) {
124
+ if (typeof value !== "string")
125
+ continue;
126
+ const trimmed = value.trim();
127
+ if (trimmed.length === 0)
128
+ continue;
129
+ next[key] = trimmed;
130
+ }
131
+ return next;
132
+ }
133
+ function parseCsv(raw) {
134
+ if (!raw)
135
+ return [];
136
+ return raw
137
+ .split(",")
138
+ .map((item) => item.trim())
139
+ .filter((item) => item.length > 0);
140
+ }
141
+ function parseAdvanceStageArgs(tokens) {
142
+ const [stageRaw, ...flagTokens] = tokens;
143
+ if (!isFlowStageValue(stageRaw)) {
144
+ throw new Error(`internal advance-stage requires a stage positional argument (${FLOW_STAGES.join(", ")}).`);
145
+ }
146
+ let evidenceJson;
147
+ let passed = [];
148
+ let waiveDelegations = [];
149
+ let waiverReason;
150
+ let quiet = false;
151
+ for (const token of flagTokens) {
152
+ if (token === "--quiet") {
153
+ quiet = true;
154
+ continue;
155
+ }
156
+ if (token.startsWith("--evidence-json=")) {
157
+ evidenceJson = token.replace("--evidence-json=", "");
158
+ continue;
159
+ }
160
+ if (token.startsWith("--passed=")) {
161
+ passed = [...passed, ...parseCsv(token.replace("--passed=", ""))];
162
+ continue;
163
+ }
164
+ if (token.startsWith("--waive-delegation=")) {
165
+ waiveDelegations = [
166
+ ...waiveDelegations,
167
+ ...parseCsv(token.replace("--waive-delegation=", ""))
168
+ ];
169
+ continue;
170
+ }
171
+ if (token.startsWith("--waiver-reason=")) {
172
+ waiverReason = token.replace("--waiver-reason=", "").trim();
173
+ continue;
174
+ }
175
+ throw new Error(`Unknown flag for internal advance-stage: ${token}`);
176
+ }
177
+ return {
178
+ stage: stageRaw,
179
+ passedGateIds: unique(passed),
180
+ evidenceByGate: parseEvidenceByGate(evidenceJson),
181
+ waiveDelegations: unique(waiveDelegations),
182
+ waiverReason,
183
+ quiet
184
+ };
185
+ }
186
+ function parseVerifyFlowStateDiffArgs(tokens) {
187
+ let afterJson;
188
+ let afterFile;
189
+ let quiet = false;
190
+ for (const token of tokens) {
191
+ if (token === "--quiet") {
192
+ quiet = true;
193
+ continue;
194
+ }
195
+ if (token.startsWith("--after-json=")) {
196
+ afterJson = token.replace("--after-json=", "");
197
+ continue;
198
+ }
199
+ if (token.startsWith("--after-file=")) {
200
+ afterFile = token.replace("--after-file=", "");
201
+ continue;
202
+ }
203
+ throw new Error(`Unknown flag for internal verify-flow-state-diff: ${token}`);
204
+ }
205
+ if (!afterJson && !afterFile) {
206
+ throw new Error("internal verify-flow-state-diff requires --after-json=<json> or --after-file=<path>.");
207
+ }
208
+ return { afterJson, afterFile, quiet };
209
+ }
210
+ function parseVerifyCurrentStateArgs(tokens) {
211
+ let quiet = false;
212
+ for (const token of tokens) {
213
+ if (token === "--quiet") {
214
+ quiet = true;
215
+ continue;
216
+ }
217
+ throw new Error(`Unknown flag for internal verify-current-state: ${token}`);
218
+ }
219
+ return { quiet };
220
+ }
221
+ async function buildValidationReport(projectRoot, flowState) {
222
+ const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
223
+ const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState);
224
+ const completedStages = verifyCompletedStagesGateClosure(flowState);
225
+ const ok = delegation.satisfied && gates.ok && gates.complete && completedStages.ok;
226
+ return {
227
+ ok,
228
+ stage: flowState.currentStage,
229
+ delegation: {
230
+ satisfied: delegation.satisfied,
231
+ missing: delegation.missing,
232
+ waived: delegation.waived,
233
+ missingEvidence: delegation.missingEvidence,
234
+ expectedMode: delegation.expectedMode
235
+ },
236
+ gates: {
237
+ ok: gates.ok,
238
+ complete: gates.complete,
239
+ issues: gates.issues,
240
+ missingRequired: gates.missingRequired,
241
+ missingTriggeredConditional: gates.missingTriggeredConditional
242
+ },
243
+ completedStages: {
244
+ ok: completedStages.ok,
245
+ issues: completedStages.issues
246
+ }
247
+ };
248
+ }
249
+ const LEARNINGS_HARVEST_MARKER_PREFIX = "<!-- cclaw:learnings-harvested:";
250
+ function withLearningsHarvestMarker(artifactMarkdown, appendedEntries, skippedDuplicates) {
251
+ const suffix = artifactMarkdown.endsWith("\n") ? "" : "\n";
252
+ return `${artifactMarkdown}${suffix}${LEARNINGS_HARVEST_MARKER_PREFIX}${new Date().toISOString()} appended=${appendedEntries} skipped=${skippedDuplicates} -->\n`;
253
+ }
254
+ async function harvestStageLearnings(projectRoot, stage, artifactFile) {
255
+ const artifactPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", artifactFile);
256
+ let raw = "";
257
+ try {
258
+ raw = await fs.readFile(artifactPath, "utf8");
259
+ }
260
+ catch (err) {
261
+ return {
262
+ ok: false,
263
+ markerWritten: false,
264
+ parsedEntries: 0,
265
+ appendedEntries: 0,
266
+ skippedDuplicates: 0,
267
+ details: `Unable to read artifact for learnings harvest (${artifactPath}): ${err instanceof Error ? err.message : String(err)}`
268
+ };
269
+ }
270
+ if (raw.includes(LEARNINGS_HARVEST_MARKER_PREFIX)) {
271
+ return {
272
+ ok: true,
273
+ markerWritten: false,
274
+ parsedEntries: 0,
275
+ appendedEntries: 0,
276
+ skippedDuplicates: 0,
277
+ details: "Learnings already harvested for this artifact."
278
+ };
279
+ }
280
+ const learningsBody = extractMarkdownSectionBody(raw, "Learnings");
281
+ if (learningsBody === null) {
282
+ return {
283
+ ok: false,
284
+ markerWritten: false,
285
+ parsedEntries: 0,
286
+ appendedEntries: 0,
287
+ skippedDuplicates: 0,
288
+ details: 'Artifact is missing required "## Learnings" section.'
289
+ };
290
+ }
291
+ const parsed = parseLearningsSection(learningsBody);
292
+ if (!parsed.ok) {
293
+ return {
294
+ ok: false,
295
+ markerWritten: false,
296
+ parsedEntries: 0,
297
+ appendedEntries: 0,
298
+ skippedDuplicates: 0,
299
+ details: parsed.details
300
+ };
301
+ }
302
+ const activeFeature = await readActiveFeature(projectRoot).catch(() => null);
303
+ const appendResult = await appendKnowledge(projectRoot, parsed.entries, {
304
+ stage,
305
+ originStage: stage,
306
+ originFeature: activeFeature,
307
+ project: path.basename(projectRoot)
308
+ });
309
+ if (appendResult.invalid > 0) {
310
+ return {
311
+ ok: false,
312
+ markerWritten: false,
313
+ parsedEntries: parsed.entries.length,
314
+ appendedEntries: appendResult.appended,
315
+ skippedDuplicates: appendResult.skippedDuplicates,
316
+ details: `Learnings append failed schema checks: ${appendResult.errors.join(" | ")}`
317
+ };
318
+ }
319
+ const withMarker = withLearningsHarvestMarker(raw, appendResult.appended, appendResult.skippedDuplicates);
320
+ await fs.writeFile(artifactPath, withMarker, "utf8");
321
+ return {
322
+ ok: true,
323
+ markerWritten: true,
324
+ parsedEntries: parsed.entries.length,
325
+ appendedEntries: appendResult.appended,
326
+ skippedDuplicates: appendResult.skippedDuplicates,
327
+ details: parsed.none
328
+ ? "Learnings section marked none; harvest marker recorded."
329
+ : `Harvested ${appendResult.appended} learning entr${appendResult.appended === 1 ? "y" : "ies"} (${appendResult.skippedDuplicates} duplicate skipped).`
330
+ };
331
+ }
332
+ async function runAdvanceStage(projectRoot, args, io) {
333
+ const flowState = await readFlowState(projectRoot);
334
+ if (flowState.currentStage !== args.stage) {
335
+ io.stderr.write(`cclaw internal advance-stage: current stage is "${flowState.currentStage}", not "${args.stage}".\n`);
336
+ return 1;
337
+ }
338
+ const schema = stageSchema(args.stage);
339
+ const requiredGateIds = schema.requiredGates
340
+ .filter((gate) => gate.tier === "required")
341
+ .map((gate) => gate.id);
342
+ const allowedGateIds = new Set(schema.requiredGates.map((gate) => gate.id));
343
+ const selectedGateIds = args.passedGateIds.length > 0
344
+ ? args.passedGateIds.filter((gateId) => allowedGateIds.has(gateId))
345
+ : requiredGateIds;
346
+ const missingRequired = requiredGateIds.filter((gateId) => !selectedGateIds.includes(gateId));
347
+ if (missingRequired.length > 0) {
348
+ io.stderr.write(`cclaw internal advance-stage: required gates not selected as passed: ${missingRequired.join(", ")}.\n`);
349
+ return 1;
350
+ }
351
+ const mandatory = new Set(schema.mandatoryDelegations);
352
+ for (const agent of args.waiveDelegations) {
353
+ if (!mandatory.has(agent)) {
354
+ io.stderr.write(`cclaw internal advance-stage: cannot waive "${agent}" for stage "${args.stage}" (not mandatory).\n`);
355
+ return 1;
356
+ }
357
+ }
358
+ if (args.waiveDelegations.length > 0) {
359
+ const waiverReason = args.waiverReason && args.waiverReason.length > 0
360
+ ? args.waiverReason
361
+ : "manual_waiver";
362
+ for (const agent of args.waiveDelegations) {
363
+ await appendDelegation(projectRoot, {
364
+ stage: args.stage,
365
+ agent,
366
+ mode: "mandatory",
367
+ status: "waived",
368
+ waiverReason,
369
+ fulfillmentMode: "role-switch",
370
+ ts: new Date().toISOString()
371
+ });
372
+ }
373
+ }
374
+ const catalog = flowState.stageGateCatalog[args.stage];
375
+ const nextPassed = unique([...catalog.passed, ...selectedGateIds]).filter((gateId) => allowedGateIds.has(gateId));
376
+ const nextBlocked = unique(catalog.blocked.filter((gateId) => !nextPassed.includes(gateId))).filter((gateId) => allowedGateIds.has(gateId));
377
+ const conditional = new Set(catalog.conditional);
378
+ const nextTriggered = unique([
379
+ ...catalog.triggered.filter((gateId) => conditional.has(gateId)),
380
+ ...nextPassed.filter((gateId) => conditional.has(gateId)),
381
+ ...nextBlocked.filter((gateId) => conditional.has(gateId))
382
+ ]);
383
+ const nextGuardEvidence = { ...flowState.guardEvidence };
384
+ for (const gateId of nextPassed) {
385
+ const existing = nextGuardEvidence[gateId];
386
+ if (typeof existing === "string" && existing.trim().length > 0)
387
+ continue;
388
+ const provided = args.evidenceByGate[gateId];
389
+ nextGuardEvidence[gateId] = provided && provided.trim().length > 0
390
+ ? provided.trim()
391
+ : `stage-complete helper auto-evidence for ${gateId} @ ${new Date().toISOString()} (${schema.artifactFile})`;
392
+ }
393
+ const nextStageCatalog = {
394
+ required: [...catalog.required],
395
+ recommended: [...catalog.recommended],
396
+ conditional: [...catalog.conditional],
397
+ triggered: nextTriggered,
398
+ passed: nextPassed,
399
+ blocked: nextBlocked
400
+ };
401
+ const candidateState = {
402
+ ...flowState,
403
+ guardEvidence: nextGuardEvidence,
404
+ stageGateCatalog: {
405
+ ...flowState.stageGateCatalog,
406
+ [args.stage]: nextStageCatalog
407
+ }
408
+ };
409
+ const validation = await buildValidationReport(projectRoot, candidateState);
410
+ if (!validation.ok) {
411
+ io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}".\n`);
412
+ if (validation.delegation.missing.length > 0) {
413
+ io.stderr.write(`- missing delegations: ${validation.delegation.missing.join(", ")}\n`);
414
+ }
415
+ if (validation.delegation.missingEvidence.length > 0) {
416
+ io.stderr.write(`- role-switch evidence missing: ${validation.delegation.missingEvidence.join(", ")}\n`);
417
+ }
418
+ if (validation.gates.issues.length > 0) {
419
+ io.stderr.write(`- gate issues: ${validation.gates.issues.join(" | ")}\n`);
420
+ }
421
+ if (validation.completedStages.issues.length > 0) {
422
+ io.stderr.write(`- completed-stage closure issues: ${validation.completedStages.issues.join(" | ")}\n`);
423
+ }
424
+ return 1;
425
+ }
426
+ const learningsHarvest = await harvestStageLearnings(projectRoot, args.stage, schema.artifactFile);
427
+ if (!learningsHarvest.ok) {
428
+ io.stderr.write(`cclaw internal advance-stage: learnings harvest failed for "${schema.artifactFile}". ${learningsHarvest.details}\n`);
429
+ return 1;
430
+ }
431
+ const successor = nextStage(args.stage, flowState.track);
432
+ const completedStages = flowState.completedStages.includes(args.stage)
433
+ ? [...flowState.completedStages]
434
+ : [...flowState.completedStages, args.stage];
435
+ const finalState = {
436
+ ...candidateState,
437
+ completedStages,
438
+ currentStage: successor ?? args.stage
439
+ };
440
+ await writeFlowState(projectRoot, finalState);
441
+ if (!args.quiet) {
442
+ io.stdout.write(`${JSON.stringify({
443
+ ok: true,
444
+ command: "advance-stage",
445
+ stage: args.stage,
446
+ nextStage: successor,
447
+ currentStage: finalState.currentStage,
448
+ completedStages: finalState.completedStages,
449
+ learnings: {
450
+ parsed: learningsHarvest.parsedEntries,
451
+ appended: learningsHarvest.appendedEntries,
452
+ skippedDuplicates: learningsHarvest.skippedDuplicates,
453
+ markerWritten: learningsHarvest.markerWritten,
454
+ details: learningsHarvest.details
455
+ }
456
+ }, null, 2)}\n`);
457
+ }
458
+ return 0;
459
+ }
460
+ async function runVerifyFlowStateDiff(projectRoot, args, io) {
461
+ let raw = args.afterJson;
462
+ if (!raw && args.afterFile) {
463
+ raw = await fs.readFile(args.afterFile, "utf8");
464
+ }
465
+ if (!raw) {
466
+ io.stderr.write("cclaw internal verify-flow-state-diff: no candidate state payload.\n");
467
+ return 1;
468
+ }
469
+ let parsed;
470
+ try {
471
+ parsed = JSON.parse(raw);
472
+ }
473
+ catch (err) {
474
+ io.stderr.write(`cclaw internal verify-flow-state-diff: invalid JSON payload (${err instanceof Error ? err.message : String(err)}).\n`);
475
+ return 1;
476
+ }
477
+ const current = await readFlowState(projectRoot);
478
+ const candidate = coerceCandidateFlowState(parsed, current);
479
+ const validation = await buildValidationReport(projectRoot, candidate);
480
+ if (!args.quiet) {
481
+ io.stdout.write(`${JSON.stringify(validation, null, 2)}\n`);
482
+ }
483
+ if (!validation.ok) {
484
+ io.stderr.write(`cclaw internal verify-flow-state-diff: candidate state is invalid for stage "${validation.stage}".\n`);
485
+ }
486
+ return validation.ok ? 0 : 1;
487
+ }
488
+ async function runVerifyCurrentState(projectRoot, args, io) {
489
+ const current = await readFlowState(projectRoot);
490
+ const validation = await buildValidationReport(projectRoot, current);
491
+ if (!args.quiet) {
492
+ io.stdout.write(`${JSON.stringify(validation, null, 2)}\n`);
493
+ }
494
+ if (!validation.ok) {
495
+ const unmetDelegations = validation.delegation.missing.length + validation.delegation.missingEvidence.length;
496
+ const gatesWithoutEvidence = validation.gates.issues.filter((issue) => issue.includes("missing guardEvidence entry")).length;
497
+ io.stderr.write(`cclaw: current stage has ${unmetDelegations} unmet mandatory delegations and ${gatesWithoutEvidence} gates without evidence.\n`);
498
+ io.stderr.write(`cclaw internal verify-current-state: unresolved stage constraints for "${validation.stage}".\n`);
499
+ }
500
+ return validation.ok ? 0 : 1;
501
+ }
502
+ export async function runInternalCommand(projectRoot, argv, io) {
503
+ const [subcommand, ...tokens] = argv;
504
+ if (!subcommand) {
505
+ io.stderr.write("cclaw internal requires a subcommand: advance-stage | verify-flow-state-diff | verify-current-state\n");
506
+ return 1;
507
+ }
508
+ try {
509
+ if (subcommand === "advance-stage") {
510
+ return await runAdvanceStage(projectRoot, parseAdvanceStageArgs(tokens), io);
511
+ }
512
+ if (subcommand === "verify-flow-state-diff") {
513
+ return await runVerifyFlowStateDiff(projectRoot, parseVerifyFlowStateDiffArgs(tokens), io);
514
+ }
515
+ if (subcommand === "verify-current-state") {
516
+ return await runVerifyCurrentState(projectRoot, parseVerifyCurrentStateArgs(tokens), io);
517
+ }
518
+ io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | verify-flow-state-diff | verify-current-state\n`);
519
+ return 1;
520
+ }
521
+ catch (err) {
522
+ io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
523
+ return 1;
524
+ }
525
+ }
@@ -0,0 +1,59 @@
1
+ import { type FlowStage } from "./types.js";
2
+ export type KnowledgeEntryType = "rule" | "pattern" | "lesson" | "compound";
3
+ export type KnowledgeEntryConfidence = "high" | "medium" | "low";
4
+ export type KnowledgeEntryUniversality = "project" | "personal" | "universal";
5
+ export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
6
+ export interface KnowledgeEntry {
7
+ type: KnowledgeEntryType;
8
+ trigger: string;
9
+ action: string;
10
+ confidence: KnowledgeEntryConfidence;
11
+ domain: string | null;
12
+ stage: FlowStage | null;
13
+ origin_stage: FlowStage | null;
14
+ origin_feature: string | null;
15
+ frequency: number;
16
+ universality: KnowledgeEntryUniversality;
17
+ maturity: KnowledgeEntryMaturity;
18
+ created: string;
19
+ first_seen_ts: string;
20
+ last_seen_ts: string;
21
+ project: string | null;
22
+ }
23
+ export interface KnowledgeSeedEntry {
24
+ type: KnowledgeEntryType;
25
+ trigger: string;
26
+ action: string;
27
+ confidence: KnowledgeEntryConfidence;
28
+ domain?: string | null;
29
+ stage?: FlowStage | null;
30
+ origin_stage?: FlowStage | null;
31
+ origin_feature?: string | null;
32
+ frequency?: number;
33
+ universality?: KnowledgeEntryUniversality;
34
+ maturity?: KnowledgeEntryMaturity;
35
+ created?: string;
36
+ first_seen_ts?: string;
37
+ last_seen_ts?: string;
38
+ project?: string | null;
39
+ }
40
+ export interface AppendKnowledgeDefaults {
41
+ stage?: FlowStage | null;
42
+ originStage?: FlowStage | null;
43
+ originFeature?: string | null;
44
+ project?: string | null;
45
+ nowIso?: string;
46
+ }
47
+ export interface AppendKnowledgeResult {
48
+ appended: number;
49
+ skippedDuplicates: number;
50
+ invalid: number;
51
+ errors: string[];
52
+ appendedEntries: KnowledgeEntry[];
53
+ }
54
+ export declare function validateKnowledgeEntry(entry: unknown): {
55
+ ok: boolean;
56
+ errors: string[];
57
+ };
58
+ export declare function materializeKnowledgeEntry(seed: KnowledgeSeedEntry, defaults?: AppendKnowledgeDefaults): KnowledgeEntry;
59
+ export declare function appendKnowledge(projectRoot: string, seeds: KnowledgeSeedEntry[], defaults?: AppendKnowledgeDefaults): Promise<AppendKnowledgeResult>;