cclaw-cli 0.46.0 → 0.46.2

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/runs.js CHANGED
@@ -374,6 +374,19 @@ async function uniqueArchiveId(projectRoot, baseId) {
374
374
  function retroArtifactPath(projectRoot) {
375
375
  return path.join(activeArtifactsPath(projectRoot), "09-retro.md");
376
376
  }
377
+ function parseIsoTimestamp(value) {
378
+ if (!value || value.trim().length === 0)
379
+ return null;
380
+ const parsed = Date.parse(value);
381
+ return Number.isFinite(parsed) ? parsed : null;
382
+ }
383
+ function inInclusiveWindow(timestamp, windowStartMs, windowEndMs) {
384
+ if (windowStartMs !== null && timestamp < windowStartMs)
385
+ return false;
386
+ if (windowEndMs !== null && timestamp > windowEndMs)
387
+ return false;
388
+ return true;
389
+ }
377
390
  async function evaluateRetroGate(projectRoot, state) {
378
391
  const required = state.completedStages.includes("ship");
379
392
  const artifactFile = retroArtifactPath(projectRoot);
@@ -387,18 +400,33 @@ async function evaluateRetroGate(projectRoot, state) {
387
400
  hasRetroArtifact = false;
388
401
  }
389
402
  }
403
+ let compoundEntries = state.retro.compoundEntries;
404
+ const windowStartMs = parseIsoTimestamp(state.closeout.retroDraftedAt);
405
+ const windowEndMs = parseIsoTimestamp(state.closeout.retroAcceptedAt) ?? parseIsoTimestamp(state.retro.completedAt);
406
+ const shouldFallbackScan = compoundEntries <= 0 && (windowStartMs !== null || windowEndMs !== null);
390
407
  const knowledgeFile = path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
391
- let compoundEntries = 0;
392
- if (await exists(knowledgeFile)) {
408
+ if (shouldFallbackScan && (await exists(knowledgeFile))) {
393
409
  try {
394
410
  const raw = await fs.readFile(knowledgeFile, "utf8");
411
+ compoundEntries = 0;
395
412
  for (const line of raw.split(/\r?\n/)) {
396
413
  const trimmed = line.trim();
397
414
  if (!trimmed)
398
415
  continue;
399
416
  try {
400
417
  const parsed = JSON.parse(trimmed);
401
- if (parsed.type === "compound") {
418
+ if (parsed.type !== "compound") {
419
+ continue;
420
+ }
421
+ const created = typeof parsed.created === "string" ? parseIsoTimestamp(parsed.created) : null;
422
+ if (created === null || !inInclusiveWindow(created, windowStartMs, windowEndMs)) {
423
+ continue;
424
+ }
425
+ const source = typeof parsed.source === "string"
426
+ ? parsed.source.trim().toLowerCase()
427
+ : null;
428
+ const legacyRetroStage = parsed.stage === "retro";
429
+ if (source === "retro" || legacyRetroStage) {
402
430
  compoundEntries += 1;
403
431
  }
404
432
  }
@@ -563,6 +591,7 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
563
591
  const archiveArtifactsPath = path.join(archivePath, "artifacts");
564
592
  let sourceState = await readFlowState(projectRoot);
565
593
  const retroGate = await evaluateRetroGate(projectRoot, sourceState);
594
+ const shipCompleted = sourceState.completedStages.includes("ship");
566
595
  const skipRetro = options.skipRetro === true;
567
596
  const skipRetroReason = options.skipRetroReason?.trim();
568
597
  if (skipRetro && (!skipRetroReason || skipRetroReason.length === 0)) {
@@ -571,6 +600,12 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
571
600
  const retroSkippedInCloseout = sourceState.closeout.retroSkipped === true &&
572
601
  typeof sourceState.closeout.retroSkipReason === "string" &&
573
602
  sourceState.closeout.retroSkipReason.trim().length > 0;
603
+ const readyForArchive = sourceState.closeout.shipSubstate === "ready_to_archive";
604
+ if (shipCompleted && !readyForArchive && !skipRetro) {
605
+ throw new Error("Archive blocked: closeout is not ready_to_archive. " +
606
+ "Resume /cc-next until closeout reaches ready_to_archive, " +
607
+ "or run `cclaw archive --skip-retro --retro-reason=<text>` for CLI-only flows.");
608
+ }
574
609
  if (retroGate.required && !retroGate.completed && !skipRetro && !retroSkippedInCloseout) {
575
610
  throw new Error("Archive blocked: retro gate is required after ship completion. " +
576
611
  "Run /cc-next (auto-runs retro) or, for CLI-only flows, re-run `cclaw archive --skip-retro --retro-reason=<text>`.");
@@ -590,8 +625,12 @@ export async function archiveRun(projectRoot, featureName, options = {}) {
590
625
  const retroSummary = {
591
626
  required: retroGate.required,
592
627
  completed: retroGate.completed,
593
- skipped: skipRetro,
594
- skipReason: skipRetro ? skipRetroReason : undefined,
628
+ skipped: skipRetro || retroSkippedInCloseout,
629
+ skipReason: skipRetro
630
+ ? skipRetroReason
631
+ : retroSkippedInCloseout
632
+ ? sourceState.closeout.retroSkipReason
633
+ : undefined,
595
634
  compoundEntries: retroGate.compoundEntries
596
635
  };
597
636
  await ensureDir(archivePath);
package/dist/tdd-cycle.js CHANGED
@@ -48,6 +48,14 @@ export function validateTddCycleOrder(entries, options = {}) {
48
48
  let state = "need_red";
49
49
  for (const entry of sliceEntries) {
50
50
  if (entry.phase === "red") {
51
+ if (entry.exitCode === undefined) {
52
+ issues.push(`slice ${slice}: red entry must record a non-zero exitCode`);
53
+ continue;
54
+ }
55
+ if (entry.exitCode === 0) {
56
+ issues.push(`slice ${slice}: red entry exitCode must be non-zero`);
57
+ continue;
58
+ }
51
59
  if (state === "red_open") {
52
60
  issues.push(`slice ${slice}: duplicate red before green`);
53
61
  continue;
@@ -56,6 +64,14 @@ export function validateTddCycleOrder(entries, options = {}) {
56
64
  continue;
57
65
  }
58
66
  if (entry.phase === "green") {
67
+ if (entry.exitCode === undefined) {
68
+ issues.push(`slice ${slice}: green entry must record exitCode 0`);
69
+ continue;
70
+ }
71
+ if (entry.exitCode !== 0) {
72
+ issues.push(`slice ${slice}: green entry exitCode must be 0`);
73
+ continue;
74
+ }
59
75
  if (state !== "red_open") {
60
76
  issues.push(`slice ${slice}: green logged before red`);
61
77
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.46.0",
3
+ "version": "0.46.2",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {