@fitlab-ai/agent-infra 0.11.3 → 0.11.5

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 (172) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -6
  4. package/dist/lib/decide.js +4 -41
  5. package/dist/lib/git/branch-worktree.js +45 -0
  6. package/dist/lib/internal/cli-route-inventory.js +1 -4
  7. package/dist/lib/internal/task-activity.js +7 -40
  8. package/dist/lib/internal/task-artifact.js +1 -1
  9. package/dist/lib/internal/task-event.js +4 -37
  10. package/dist/lib/internal/task-finalization.js +48 -1
  11. package/dist/lib/internal/task-ledger.js +14 -44
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +2 -4
  14. package/dist/lib/internal/task-review.js +5 -37
  15. package/dist/lib/internal/task-verify.js +3 -47
  16. package/dist/lib/internal/task-warning.js +4 -40
  17. package/dist/lib/platform/completion-backfill.js +63 -17
  18. package/dist/lib/platform/pr-summary.js +4 -23
  19. package/dist/lib/platform/verification-sync.js +12 -49
  20. package/dist/lib/sandbox/control/client.js +4 -1
  21. package/dist/lib/sandbox/control/executor.js +12 -4
  22. package/dist/lib/sandbox/control/protocol.js +2 -1
  23. package/dist/lib/sandbox/control/server.js +26 -143
  24. package/dist/lib/sandbox/removal.js +15 -8
  25. package/dist/lib/sandbox/task-cleanup.js +13 -20
  26. package/dist/lib/task/activity-intent.js +1 -2
  27. package/dist/lib/task/artifact-command.js +11 -4
  28. package/dist/lib/task/artifact-lifecycle.js +217 -85
  29. package/dist/lib/task/artifact-operations.js +37 -28
  30. package/dist/lib/task/artifact-receipts.js +4 -1
  31. package/dist/lib/task/artifact-schema.js +8 -7
  32. package/dist/lib/task/artifact-selection.js +25 -0
  33. package/dist/lib/task/capabilities.js +158 -39
  34. package/dist/lib/task/commands/status.js +9 -2
  35. package/dist/lib/task/control-authority.js +12 -168
  36. package/dist/lib/task/decision-intents.js +14 -4
  37. package/dist/lib/task/delivery.js +1 -43
  38. package/dist/lib/task/events.js +68 -73
  39. package/dist/lib/task/finalization-handoff.js +92 -0
  40. package/dist/lib/task/finalization.js +423 -154
  41. package/dist/lib/task/intermediate-cleanup.js +9 -148
  42. package/dist/lib/task/ledger-intents.js +159 -20
  43. package/dist/lib/task/lifecycle-path.js +75 -0
  44. package/dist/lib/task/lifecycle.js +60 -11
  45. package/dist/lib/task/local-artifact-finalization.js +13 -47
  46. package/dist/lib/task/orchestration.js +17 -7
  47. package/dist/lib/task/review-command.js +1 -2
  48. package/dist/lib/task/review-finalization.js +4 -18
  49. package/dist/lib/task/rework-intent.js +70 -4
  50. package/dist/lib/task/snapshot.js +6 -6
  51. package/dist/lib/task/verification-engine.js +25 -30
  52. package/dist/lib/task/verification.js +2 -13
  53. package/dist/lib/task/workflow-warning-intents.js +23 -10
  54. package/dist/lib/task/workspace-snapshot.js +7 -2
  55. package/dist/lib/task/write.js +1 -2
  56. package/dist/lifecycle-build-manifest.json +3 -3
  57. package/lib/decide.ts +5 -35
  58. package/lib/git/branch-worktree.ts +41 -0
  59. package/lib/internal/cli-route-inventory.ts +1 -3
  60. package/lib/internal/task-activity.ts +6 -34
  61. package/lib/internal/task-artifact.ts +1 -1
  62. package/lib/internal/task-event.ts +4 -37
  63. package/lib/internal/task-finalization.ts +47 -1
  64. package/lib/internal/task-ledger.ts +15 -41
  65. package/lib/internal/task-lifecycle.ts +2 -2
  66. package/lib/internal/task-operation-registry.ts +2 -4
  67. package/lib/internal/task-review.ts +5 -31
  68. package/lib/internal/task-verify.ts +3 -39
  69. package/lib/internal/task-warning.ts +3 -32
  70. package/lib/platform/completion-backfill.ts +78 -16
  71. package/lib/platform/pr-summary.ts +4 -25
  72. package/lib/platform/verification-sync.ts +13 -53
  73. package/lib/sandbox/control/client.ts +5 -1
  74. package/lib/sandbox/control/executor.ts +13 -4
  75. package/lib/sandbox/control/protocol.ts +3 -2
  76. package/lib/sandbox/control/server.ts +21 -146
  77. package/lib/sandbox/removal.ts +23 -7
  78. package/lib/sandbox/task-cleanup.ts +12 -20
  79. package/lib/task/activity-intent.ts +1 -2
  80. package/lib/task/artifact-command.ts +14 -4
  81. package/lib/task/artifact-lifecycle.ts +236 -85
  82. package/lib/task/artifact-operations.ts +59 -28
  83. package/lib/task/artifact-receipts.ts +4 -1
  84. package/lib/task/artifact-schema.ts +10 -7
  85. package/lib/task/artifact-selection.ts +53 -0
  86. package/lib/task/capabilities.ts +154 -40
  87. package/lib/task/commands/status.ts +11 -2
  88. package/lib/task/control-authority.ts +17 -189
  89. package/lib/task/decision-intents.ts +11 -4
  90. package/lib/task/delivery.ts +1 -40
  91. package/lib/task/events.ts +73 -74
  92. package/lib/task/finalization-handoff.ts +104 -0
  93. package/lib/task/finalization.ts +388 -122
  94. package/lib/task/intermediate-cleanup.ts +9 -125
  95. package/lib/task/ledger-intents.ts +157 -20
  96. package/lib/task/lifecycle-path.ts +98 -0
  97. package/lib/task/lifecycle.ts +58 -13
  98. package/lib/task/local-artifact-finalization.ts +14 -56
  99. package/lib/task/orchestration.ts +26 -8
  100. package/lib/task/review-command.ts +2 -5
  101. package/lib/task/review-finalization.ts +4 -20
  102. package/lib/task/rework-intent.ts +75 -5
  103. package/lib/task/snapshot.ts +6 -6
  104. package/lib/task/verification-engine.ts +29 -30
  105. package/lib/task/verification.ts +3 -16
  106. package/lib/task/workflow-warning-intents.ts +20 -10
  107. package/lib/task/workspace-snapshot.ts +3 -2
  108. package/lib/task/write.ts +1 -4
  109. package/package.json +1 -1
  110. package/templates/.agents/rules/review-handshake.en.md +4 -4
  111. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  112. package/templates/.agents/skills/analyze-task/SKILL.en.md +21 -5
  113. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
  114. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  115. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  116. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  117. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  118. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  119. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  120. package/templates/.agents/skills/code-task/reference/report-template.en.md +7 -7
  121. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
  122. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  123. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  124. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  125. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  126. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  127. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  128. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  129. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  130. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  131. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  132. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  133. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  134. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  135. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  136. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  137. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  138. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  139. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  140. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  141. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  142. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  143. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  144. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  145. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  146. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  147. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  148. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  149. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  150. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  151. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  152. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  153. package/templates/.agents/templates/task.en.md +2 -2
  154. package/templates/.agents/templates/task.zh-CN.md +2 -2
  155. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  156. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  157. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  158. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  159. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  160. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  161. package/dist/lib/internal/task-override.js +0 -138
  162. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  163. package/dist/lib/task/artifact-recovery.js +0 -476
  164. package/dist/lib/task/artifact-repair-intent.js +0 -181
  165. package/dist/lib/task/guard-override.js +0 -31
  166. package/dist/lib/task/human-override.js +0 -1259
  167. package/lib/internal/task-override.ts +0 -144
  168. package/lib/sandbox/control/finalization-response.ts +0 -35
  169. package/lib/task/artifact-recovery.ts +0 -572
  170. package/lib/task/artifact-repair-intent.ts +0 -227
  171. package/lib/task/guard-override.ts +0 -50
  172. package/lib/task/human-override.ts +0 -1522
@@ -43,9 +43,6 @@ const lifecycleProducerCatalog = lifecycleFailureCatalog.map((code) => ({
43
43
  guardId: 'G-03',
44
44
  code
45
45
  }));
46
- function allowsManualOverride(options, code) {
47
- return options.manualOverride?.failureId === `lifecycle.apply:${code}`;
48
- }
49
46
  const STEPS = ['task-written', 'directory-moved', 'registry-committed'];
50
47
  const DEFAULT_IO = {
51
48
  renameSync: (source, target) => fs.renameSync(source, target),
@@ -320,6 +317,49 @@ export function readLifecycleJournalEvidence(repoRoot, taskId) {
320
317
  return { exists: true, completedSteps: [], failure: 'SANDBOX_CONTROL_LIFECYCLE_JOURNAL_INVALID' };
321
318
  }
322
319
  }
320
+ export function inspectTaskLifecycleProgress(repoRoot, taskId, agent) {
321
+ try {
322
+ const hot = locateHotTaskDirs(repoRoot, taskId);
323
+ if (hot.length !== 1 || (hot[0].state !== 'active' && hot[0].state !== 'completed'))
324
+ return 'unknown';
325
+ const { state, taskDir } = hot[0];
326
+ const content = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
327
+ const frontmatter = parseTypedTaskFrontmatter(content);
328
+ if (frontmatter.id !== taskId)
329
+ return 'unknown';
330
+ const journalPath = path.join(taskDir, '.task-lifecycle.json');
331
+ if (!fs.existsSync(journalPath)) {
332
+ if (state === 'active')
333
+ return frontmatter.status === 'active' ? 'not-started' : 'unknown';
334
+ return matchingCompletion(content, { taskRef: taskId, intent: 'complete', agent })
335
+ ? 'started-recoverable' : 'unknown';
336
+ }
337
+ const journal = parseJournal(fs.readFileSync(journalPath, 'utf8'));
338
+ const request = { taskRef: taskId, intent: 'complete', agent };
339
+ if (journal.taskId !== taskId || journal.intent !== 'complete'
340
+ || journal.intentDigest !== intentIdentity(request, taskId)
341
+ || journal.sourceState !== 'active' || journal.targetState !== 'completed')
342
+ return 'unknown';
343
+ if (journal.completedSteps.some((step, index) => STEPS[index] !== step))
344
+ return 'unknown';
345
+ const directoryMoved = journal.completedSteps.includes('directory-moved');
346
+ const registryCommitted = journal.completedSteps.includes('registry-committed');
347
+ const hasShortId = loadShortIdByTaskId(repoRoot).has(taskId);
348
+ if (state === 'active' && (directoryMoved || !hasShortId))
349
+ return 'unknown';
350
+ if (state === 'completed' && (!journal.completedSteps.includes('task-written')
351
+ || !matchingCompletion(content, request) || (registryCommitted && hasShortId)))
352
+ return 'unknown';
353
+ if (frontmatter.status !== state
354
+ && !(state === 'active' && frontmatter.status === 'completed' && matchingCompletion(content, request))) {
355
+ return 'unknown';
356
+ }
357
+ return 'started-recoverable';
358
+ }
359
+ catch {
360
+ return 'unknown';
361
+ }
362
+ }
323
363
  function directoryManifest(root) {
324
364
  const entries = [];
325
365
  const visit = (current, relative = '') => {
@@ -440,7 +480,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
440
480
  }
441
481
  return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: `task ${taskId} is already ${spec.target} with a different lifecycle intent` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
442
482
  }
443
- if (!spec.sources.includes(sourceState) && !existingJournal && !allowsManualOverride(options, 'LIFECYCLE_SOURCE_INVALID')) {
483
+ if (!spec.sources.includes(sourceState) && !existingJournal) {
444
484
  return failed(request, { code: 'LIFECYCLE_SOURCE_INVALID', message: `${request.intent} is not allowed from ${sourceState}` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
445
485
  }
446
486
  if (sourcePath !== targetPath && fs.existsSync(targetPath) && !existingJournal) {
@@ -456,7 +496,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
456
496
  }
457
497
  if (frontmatter.id !== taskId)
458
498
  return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
459
- if (!locateActivityLog(content) && !allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'))
499
+ if (!locateActivityLog(content))
460
500
  return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
461
501
  if (spec.target === 'active') {
462
502
  const contract = validateCurrentTaskContract(content);
@@ -472,10 +512,10 @@ function applyTaskLifecycle(requestInput, options = {}) {
472
512
  }
473
513
  const registry = JSON.parse(registryRequest.output || '{"version":1,"ids":{}}');
474
514
  const registeredKey = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId)?.[0];
475
- if (spec.registry === 'release' && sourceState === 'active' && !registeredKey && !allowsManualOverride(options, 'LIFECYCLE_SHORT_ID_PRECONDITION')) {
515
+ if (spec.registry === 'release' && sourceState === 'active' && !registeredKey) {
476
516
  return failed(request, { code: 'LIFECYCLE_SHORT_ID_PRECONDITION', message: 'active source has no short-id registry entry' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
477
517
  }
478
- if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1 && !allowsManualOverride(options, 'SHORT_ID_CAPACITY_EXCEEDED')) {
518
+ if (spec.registry === 'alloc' && !registeredKey && Object.keys(registry.ids).length >= 10 ** configuredShortIdLength(repoRoot) - 1) {
479
519
  return failed(request, { code: 'SHORT_ID_CAPACITY_EXCEEDED', message: 'short-id registry has no free slot' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
480
520
  }
481
521
  const digest = intentIdentity(request, taskId);
@@ -499,8 +539,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
499
539
  return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
500
540
  return failed(request, journal.failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, completedSteps: journal.completedSteps, pendingSteps: STEPS.filter((step) => !journal.completedSteps.includes(step)) });
501
541
  }
502
- const overrideResumesRecordedFailure = journal.failure && allowsManualOverride(options, journal.failure.code);
503
- if (journal.intentDigest !== digest && !overrideResumesRecordedFailure)
542
+ if (journal.intentDigest !== digest)
504
543
  return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
505
544
  }
506
545
  else {
@@ -516,7 +555,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
516
555
  }
517
556
  journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
518
557
  if (request.dryRun) {
519
- const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
558
+ const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, false);
520
559
  if ('code' in plannedMutations)
521
560
  return failed(request, plannedMutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
522
561
  const taskPlan = writeTask({ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations: plannedMutations, dryRun: true }, {
@@ -558,7 +597,7 @@ function applyTaskLifecycle(requestInput, options = {}) {
558
597
  completed.add('task-written');
559
598
  if (!completed.has('task-written')) {
560
599
  const current = io.readFileSync(path.join(sourcePath, 'task.md'));
561
- const mutations = mutationsFor(request, current, journal.metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
600
+ const mutations = mutationsFor(request, current, journal.metadata, restoredFiles, false);
562
601
  if ('code' in mutations)
563
602
  return failed(request, mutations, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, completedSteps: [...completed], changed: completed.size > 0 });
564
603
  const writeResult = writeTask({ taskRef: taskId, expectedState: sourceState === 'blocked' ? 'blocked' : 'active', mutations }, {
@@ -578,6 +617,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
578
617
  return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
579
618
  }
580
619
  }
620
+ if (options.prepareOnly && !completed.has('directory-moved')) {
621
+ return {
622
+ ...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
623
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
624
+ task: { operations: taskOperations }, directory: { effect: 'unchanged', changed: false },
625
+ shortId: { effect: 'unchanged', shortId: null, changed: false },
626
+ timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
627
+ journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
628
+ };
629
+ }
581
630
  if (fs.existsSync(targetPath) && !fs.existsSync(sourcePath))
582
631
  completed.add('directory-moved');
583
632
  if (!completed.has('directory-moved')) {
@@ -1,12 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
4
- import { scanVisibleMarkdown } from './markdown.js';
5
4
  import { parseArtifactName } from './artifact-name.js';
6
- import { hasOpenArtifactRound, } from './artifact-lifecycle.js';
5
+ import { parseLifecyclePathDecision } from './lifecycle-path.js';
7
6
  import { resolveTaskRef } from './resolve-ref.js';
8
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
9
- import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
7
+ import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
10
8
  import { getArtifactSchema } from './artifact-schema.js';
11
9
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
12
10
  analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
@@ -18,59 +16,29 @@ function localDiagnosticCode(code) {
18
16
  ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
19
17
  ARTIFACT_MISSING_SECTION: 'LOCAL_ARTIFACT_MISSING_SECTION',
20
18
  ARTIFACT_DUPLICATE_SECTION: 'LOCAL_ARTIFACT_DUPLICATE_SECTION',
21
- ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
19
+ ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION',
20
+ ARTIFACT_STATE_CHECK_PATTERN_MISSING: 'LOCAL_STATUS_COMMAND_MISSING',
21
+ ARTIFACT_REQUIRED_PATTERN_MISSING: 'LOCAL_REQUIRED_PATTERN_MISSING'
22
22
  };
23
23
  return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
24
24
  }
25
25
  function localDiagnostic(item) {
26
26
  return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
27
27
  }
28
- function isStatusPattern(pattern) {
29
- return pattern === '^\\$ ';
30
- }
31
28
  function validateLocalArtifact(content, options) {
32
29
  const schema = getArtifactSchema(options.family);
33
30
  const diagnostics = [];
34
- const scanned = scanVisibleMarkdown(content);
35
- const structure = inspectArtifactStructure(content, schema);
36
- for (const item of structure.diagnostics)
31
+ const inspection = inspectArtifactContract(content, schema, { scopeStateCheckSection: true });
32
+ for (const item of inspection.diagnostics)
37
33
  diagnostics.push(localDiagnostic(item));
38
- const statusSection = schema.sections.find((section) => section.id === 'state-check');
39
- if (statusSection) {
40
- const statusHeading = scanned.headings.find((heading) => (heading.level === 2 && [statusSection.headings.zh, statusSection.headings.en].some((name) => (heading.text === name || heading.text === `${name}:` || heading.text === `${name}:`))));
41
- if (statusHeading) {
42
- const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
43
- const body = content.slice(statusHeading.end, next?.start ?? content.length);
44
- for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
45
- if (!new RegExp(pattern, 'm').test(body)) {
46
- diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, line: content.slice(0, statusHeading.start).split('\n').length });
47
- }
48
- }
49
- }
50
- }
51
- const patternInspection = inspectArtifactPatterns(content, {
52
- ...schema,
53
- requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
54
- });
55
- for (const item of patternInspection.diagnostics)
56
- diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
57
34
  const decisionDetails = inspectDecisionDetailDuplicates(content);
58
35
  if (!decisionDetails.ok)
59
36
  diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
60
- if (options.taskContent !== undefined) {
61
- const identity = options.artifact ? parseArtifactName(options.artifact) : null;
62
- const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
63
- ? expectedQualificationRelations(options.taskContent, options.family)
64
- : undefined;
65
- if (expected && !expected.ok)
66
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
67
- const qualification = validateQualificationAudit(options.taskContent, content, {
68
- family: options.family,
69
- artifact: options.artifact,
70
- expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
71
- });
72
- if (!qualification.ok)
73
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
37
+ if (options.family === 'analysis') {
38
+ const flowDecision = parseLifecyclePathDecision(content);
39
+ if (flowDecision.status !== 'valid') {
40
+ diagnostics.push({ code: 'LOCAL_FLOW_DECISION_INVALID', message: flowDecision.message, line: null });
41
+ }
74
42
  }
75
43
  return {
76
44
  ok: diagnostics.length === 0,
@@ -98,10 +66,8 @@ function prepareLocalArtifact(request, candidate) {
98
66
  const parsed = parseArtifactName(request.artifact);
99
67
  if (!parsed || parsed.family !== request.family)
100
68
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
101
- let taskContent;
102
69
  let content;
103
70
  try {
104
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
105
71
  const artifactPath = path.join(resolved.taskDir, request.artifact);
106
72
  if (!fs.statSync(artifactPath).isFile())
107
73
  return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
@@ -114,7 +80,7 @@ function prepareLocalArtifact(request, candidate) {
114
80
  return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
115
81
  }
116
82
  const validation = validateLocalArtifact(content, {
117
- family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
83
+ family: request.family, requiredSections: request.requiredSections
118
84
  });
119
85
  const artifactSha256 = sha256Content(content);
120
86
  const result = failedFinalization(request, {
@@ -22,6 +22,7 @@ import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js'
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
23
  import { reconcileTaskInvalidation } from './invalidation-command.js';
24
24
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
25
+ import { resolveArtifactContext } from './artifact-lifecycle.js';
25
26
  function supportsLifecycleDelegation(client) {
26
27
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
27
28
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -479,17 +480,13 @@ function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead)
479
480
  function routeFromFacts(facts) {
480
481
  const recommendation = recommendNext(facts);
481
482
  if (!recommendation.action)
482
- return { completion: true };
483
+ return recommendation.reasonCode === 'LIFECYCLE_REVIEWED' ? { completion: true } : null;
483
484
  const action = recommendation.action;
484
485
  if (action === 'manual-validation' || action === 'validation-run')
485
486
  return null;
486
- const family = action === 'analysis' ? 'analysis' : action;
487
- const round = maxArtifactRound([
488
- ...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])
489
- ], family) + 1;
490
487
  const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
491
488
  const role = action.startsWith('review-') ? 'reviewer' : 'executor';
492
- return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
489
+ return { action: command, role, stage: action };
493
490
  }
494
491
  function routeOrchestration(taskRef, options = {}) {
495
492
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -619,8 +616,19 @@ function routeOrchestration(taskRef, options = {}) {
619
616
  return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
620
617
  }
621
618
  const action = routed.stage;
619
+ const family = action === 'analysis' ? 'analysis' : action;
620
+ const artifactContext = resolveArtifactContext(taskRef, family, {
621
+ repoRoot: resolved.repoRoot
622
+ });
623
+ if (artifactContext.status !== 'ready' || !artifactContext.selection) {
624
+ return failed(artifactContext.error?.code ?? 'ORCHESTRATION_ARTIFACT_SELECTION_FAILED', artifactContext.error?.message ?? 'cannot determine artifact selection', resolved.taskId);
625
+ }
626
+ if (artifactContext.selection.disposition === 'reuse') {
627
+ return failed('ORCHESTRATION_REUSE_STALLED', `lifecycle still routes to ${action} after reusing ${artifactContext.selection.artifact.name}`, resolved.taskId);
628
+ }
629
+ const selected = artifactContext.selection.artifact;
622
630
  const capability = canStart(action, facts.facts, {
623
- initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
631
+ initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${selected.round}`,
624
632
  requestedAction: action, reasonCode: 'user-request', explicitRequest: true
625
633
  });
626
634
  if (!capability.allowed)
@@ -628,6 +636,8 @@ function routeOrchestration(taskRef, options = {}) {
628
636
  const policy = run ? rolePolicy(run, routed.role) : null;
629
637
  const next = {
630
638
  ...routed,
639
+ round: selected.round,
640
+ artifact: selected.name,
631
641
  requestedModel: policy?.model ?? null,
632
642
  requestedReasoningEffort: policy?.reasoningEffort ?? null
633
643
  };
@@ -3,8 +3,7 @@ export function parseReviewCommand(args) {
3
3
  throw new Error('task ref and a supported review operation are required');
4
4
  const values = {};
5
5
  const flags = {
6
- '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
7
- '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
6
+ '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
8
7
  };
9
8
  for (let index = 2; index < args.length; index += 1) {
10
9
  const flag = args[index];
@@ -9,10 +9,8 @@ import { inspectDecisionDetailDuplicates } from './decision-details.js';
9
9
  import { resolveTaskRef } from './resolve-ref.js';
10
10
  import { validateLifecycleExecution } from './lifecycle-execution.js';
11
11
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
12
- import { allowsManualOverride } from './guard-override.js';
13
12
  import { getArtifactSchema } from './artifact-schema.js';
14
13
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
16
14
  const STAGES = {
17
15
  analysis: { family: 'review-analysis' },
18
16
  plan: { family: 'review-plan' },
@@ -66,10 +64,8 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
66
64
  const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
67
65
  if (publicationError)
68
66
  return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
69
- let taskContent;
70
67
  let content;
71
68
  try {
72
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
73
69
  const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
74
70
  if (!validated.ok)
75
71
  return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
@@ -89,22 +85,12 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
89
85
  const semanticDigest = canonicalSemanticDigest(content);
90
86
  const detail = inspectDecisionDetailDuplicates(content);
91
87
  const structure = inspectArtifactContract(content, getArtifactSchema(spec.family));
92
- const expected = expectedQualificationRelations(taskContent, spec.family);
93
- if (!expected.ok)
94
- return preflightFailed(request, 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', `${expected.code}: ${expected.message}`, resolved.taskId, { artifactSha256, semanticDigest });
95
- const qualification = validateQualificationAudit(taskContent, content, {
96
- family: spec.family,
97
- artifact: request.artifact,
98
- expectedUpstreamRelations: expected.relations
99
- });
100
- const qualificationError = qualification.ok ? null : qualification;
101
- if (!detail.ok || !structure.ok || qualificationError) {
88
+ if (!detail.ok || !structure.ok) {
102
89
  const diagnostics = [
103
90
  ...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
104
- ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
105
- ...(qualificationError ? [`${qualificationError.code}: ${qualificationError.message}`] : [])
91
+ ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`)
106
92
  ].join('; ');
107
- return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
93
+ return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : 'REVIEW_ARTIFACT_STRUCTURE_INVALID', diagnostics, resolved.taskId, {
108
94
  artifactSha256, semanticDigest,
109
95
  });
110
96
  }
@@ -151,7 +137,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
151
137
  taskId = resolved.taskId ?? null;
152
138
  if (!resolved.ok)
153
139
  return reject(resolved.code, resolved.message);
154
- if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
140
+ if (resolved.state !== 'active') {
155
141
  return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
156
142
  }
157
143
  const parsed = parseArtifactName(request.artifact);
@@ -1,5 +1,9 @@
1
1
  const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'];
2
2
  const REWORK_INTENT_COLUMNS = [
3
+ 'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
+ 'classification', 'evidence_digest', 'task_fact_digest', 'status', 'declared_at', 'consumed_at'
5
+ ];
6
+ const LEGACY_REWORK_INTENT_COLUMNS = [
3
7
  'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
8
  'status', 'declared_at', 'consumed_at'
5
9
  ];
@@ -55,12 +59,16 @@ function parseReworkIntentDocument(content) {
55
59
  throw new Error(`invalid intent id '${row.intent_id}'`);
56
60
  if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
57
61
  throw new Error(`invalid finding id '${row.finding_id}'`);
58
- if (!['analysis', 'plan', 'code'].includes(row.target))
62
+ if (!['analysis', 'plan', 'code', 'pause'].includes(row.target))
59
63
  throw new Error(`invalid rework target '${row.target}'`);
64
+ if (!['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence'].includes(row.classification))
65
+ throw new Error(`invalid rework classification '${row.classification}'`);
60
66
  if (!['pending', 'consumed', 'superseded'].includes(row.status))
61
67
  throw new Error(`invalid rework status '${row.status}'`);
62
68
  if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
63
69
  throw new Error(`invalid source hash for '${row.intent_id}'`);
70
+ if (!/^[a-f0-9]{64}$/.test(row.evidence_digest) || !/^[a-f0-9]{64}$/.test(row.task_fact_digest))
71
+ throw new Error(`invalid semantic digest for '${row.intent_id}'`);
64
72
  if (!row.source_artifact || !row.declared_at)
65
73
  throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
66
74
  const terminal = row.status === 'consumed' || row.status === 'superseded';
@@ -69,6 +77,8 @@ function parseReworkIntentDocument(content) {
69
77
  return {
70
78
  intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact,
71
79
  sourceSha256: row.source_sha256, target: row.target,
80
+ classification: row.classification,
81
+ evidenceDigest: row.evidence_digest, taskFactDigest: row.task_fact_digest,
72
82
  status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
73
83
  };
74
84
  });
@@ -91,7 +101,10 @@ function renderReworkIntents(intents) {
91
101
  `| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
92
102
  ...intents.map((intent) => `| ${[
93
103
  intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
94
- intent.target, intent.status, intent.declaredAt, intent.consumedAt
104
+ intent.target, intent.classification ?? (intent.target === 'analysis' ? 'scope-or-requirement' : intent.target === 'plan' ? 'design' : intent.target === 'code' ? 'implementation' : 'insufficient-evidence'),
105
+ intent.evidenceDigest ?? requireDigest(`${intent.findingId}|${intent.sourceArtifact}|${intent.sourceSha256}`),
106
+ intent.taskFactDigest ?? requireDigest(`current|${intent.findingId}|${intent.target}`),
107
+ intent.status, intent.declaredAt, intent.consumedAt
95
108
  ].map(escape).join(' | ')} |`)
96
109
  ].join('\n');
97
110
  }
@@ -99,11 +112,63 @@ function upsertReworkIntent(intents, intent) {
99
112
  const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
100
113
  if (!existing)
101
114
  return { changed: true, intents: [...intents, intent] };
102
- const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
115
+ const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target, value.classification, value.evidenceDigest, value.taskFactDigest];
103
116
  if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent)))
104
117
  throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
105
118
  return { changed: false, intents };
106
119
  }
120
+ function consumeHumanDecisionReworkIntents(intents, findingId, consumedAt) {
121
+ let changed = false;
122
+ const next = intents.map((intent) => {
123
+ if (intent.status !== 'pending' || intent.target !== 'pause' || intent.classification !== 'human-decision' || intent.findingId !== findingId)
124
+ return intent;
125
+ changed = true;
126
+ return { ...intent, status: 'consumed', consumedAt };
127
+ });
128
+ return { changed, intents: next };
129
+ }
130
+ function parseLegacyReworkIntentDocument(content) {
131
+ // TODO(compat): Remove this explicit decoder once no task contains the eight-column rework-intent header.
132
+ const body = sectionBody(content);
133
+ if (body === null)
134
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'legacy rework intent section is missing' };
135
+ const lines = body.split(/\r?\n/).filter((line) => line.trim());
136
+ const header = lines[0] ? cells(lines[0]) : null;
137
+ const separator = lines[1] ? cells(lines[1]) : null;
138
+ if (!header || !separator || header.length !== LEGACY_REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === LEGACY_REWORK_INTENT_COLUMNS[index])) {
139
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section is not the known legacy table' };
140
+ }
141
+ try {
142
+ const intents = lines.slice(2).map((line) => {
143
+ const values = cells(line);
144
+ if (!values || values.length !== LEGACY_REWORK_INTENT_COLUMNS.length)
145
+ throw new Error('legacy rework intent row has the wrong column count');
146
+ const row = Object.fromEntries(LEGACY_REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]]));
147
+ if (!/^RI-[1-9]\d*$/.test(row.intent_id) || !/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
148
+ throw new Error('legacy rework intent identity is invalid');
149
+ if (!['analysis', 'plan', 'code'].includes(row.target) || !['pending', 'consumed', 'superseded'].includes(row.status))
150
+ throw new Error('legacy rework intent state is invalid');
151
+ if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
152
+ throw new Error('legacy rework intent source hash is invalid');
153
+ const legacyDigest = (value) => requireDigest(value);
154
+ const classification = row.target === 'analysis' ? 'scope-or-requirement' : row.target === 'plan' ? 'design' : 'implementation';
155
+ return {
156
+ intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact, sourceSha256: row.source_sha256,
157
+ target: row.target, classification,
158
+ evidenceDigest: legacyDigest(`${row.finding_id}|${row.source_artifact}|${row.source_sha256}`),
159
+ taskFactDigest: legacyDigest(`legacy|${row.finding_id}|${row.target}`),
160
+ status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
161
+ };
162
+ });
163
+ return { ok: true, present: true, intents };
164
+ }
165
+ catch (error) {
166
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
167
+ }
168
+ }
169
+ function requireDigest(value) {
170
+ return createHash('sha256').update(value).digest('hex');
171
+ }
107
172
  function consumeReworkIntents(intents, target, sourceHashes, consumedAt) {
108
173
  let changed = false;
109
174
  const next = intents.map((intent) => {
@@ -134,5 +199,6 @@ function reworkIntentMutation(content, intents) {
134
199
  body: renderReworkIntents(intents)
135
200
  };
136
201
  }
137
- export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent };
202
+ export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent, consumeHumanDecisionReworkIntents, parseLegacyReworkIntentDocument };
203
+ import { createHash } from 'node:crypto';
138
204
  //# sourceMappingURL=rework-intent.js.map
@@ -56,25 +56,25 @@ function collectTaskSnapshot(taskRef, options = {}) {
56
56
  const observations = [];
57
57
  try {
58
58
  const output = stripOneTrailingNewline((options.gitStatus ?? defaultGitStatus)(resolved.repoRoot));
59
- observations.push({ name: 'git', status: 'ready', command: '$ git status -s', output, error: null });
59
+ observations.push({ name: 'git', status: 'ready', command: 'git status --short --untracked-files=all', output, error: null });
60
60
  }
61
61
  catch (error) {
62
- return failedSnapshot(resolved, taskRef, observations, 'git', '$ git status -s', 'SNAPSHOT_GIT_FAILED', error);
62
+ return failedSnapshot(resolved, taskRef, observations, 'git', 'git status --short --untracked-files=all', 'SNAPSHOT_GIT_FAILED', error);
63
63
  }
64
64
  try {
65
65
  const entries = (options.readDirectory ?? ((dir) => fs.readdirSync(dir, { withFileTypes: true })))(resolved.taskDir);
66
66
  const output = renderDirectory(entries, resolved.taskDir, options.lstat ?? fs.lstatSync);
67
- observations.push({ name: 'task-directory', status: 'ready', command: `$ ls -la ${taskDirPath}/`, output, error: null });
67
+ observations.push({ name: 'task-directory', status: 'ready', command: `fs.readdirSync ${taskDirPath}/`, output, error: null });
68
68
  }
69
69
  catch (error) {
70
- return failedSnapshot(resolved, taskRef, observations, 'task-directory', `$ ls -la ${taskDirPath}/`, 'SNAPSHOT_DIRECTORY_READ_FAILED', error);
70
+ return failedSnapshot(resolved, taskRef, observations, 'task-directory', `fs.readdirSync ${taskDirPath}/`, 'SNAPSHOT_DIRECTORY_READ_FAILED', error);
71
71
  }
72
72
  try {
73
73
  const content = (options.readTask ?? ((file) => fs.readFileSync(file, 'utf8')))(resolved.taskMdPath);
74
- observations.push({ name: 'task-tail', status: 'ready', command: `$ tail ${taskPath}`, output: renderTail(content), error: null });
74
+ observations.push({ name: 'task-tail', status: 'ready', command: `fs.readFileSync ${taskPath} (last 10 lines)`, output: renderTail(content), error: null });
75
75
  }
76
76
  catch (error) {
77
- return failedSnapshot(resolved, taskRef, observations, 'task-tail', `$ tail ${taskPath}`, 'SNAPSHOT_TASK_READ_FAILED', error);
77
+ return failedSnapshot(resolved, taskRef, observations, 'task-tail', `fs.readFileSync ${taskPath} (last 10 lines)`, 'SNAPSHOT_TASK_READ_FAILED', error);
78
78
  }
79
79
  return {
80
80
  status: 'ready', changed: false, requestRef: taskRef, taskId: resolved.taskId,