@fitlab-ai/agent-infra 0.11.4 → 0.11.6

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 (149) hide show
  1. package/dist/bin/internal-cli.js +0 -5
  2. package/dist/lib/decide.js +4 -41
  3. package/dist/lib/git/branch-worktree.js +45 -0
  4. package/dist/lib/internal/cli-route-inventory.js +0 -3
  5. package/dist/lib/internal/task-activity.js +7 -40
  6. package/dist/lib/internal/task-event.js +4 -37
  7. package/dist/lib/internal/task-finalization.js +9 -2
  8. package/dist/lib/internal/task-ledger.js +5 -43
  9. package/dist/lib/internal/task-lifecycle.js +1 -1
  10. package/dist/lib/internal/task-operation-registry.js +1 -4
  11. package/dist/lib/internal/task-review.js +5 -37
  12. package/dist/lib/internal/task-verify.js +3 -47
  13. package/dist/lib/internal/task-warning.js +4 -40
  14. package/dist/lib/merge.js +62 -10
  15. package/dist/lib/platform/github-provider.js +9 -14
  16. package/dist/lib/platform/github-release-notes.js +80 -11
  17. package/dist/lib/platform/none-provider.js +1 -1
  18. package/dist/lib/platform/pr-summary.js +4 -23
  19. package/dist/lib/platform/provider-contract.d.ts +39 -16
  20. package/dist/lib/platform/provider-contract.js +1 -1
  21. package/dist/lib/platform/provider-validation.js +83 -13
  22. package/dist/lib/platform/release-notes.js +7 -8
  23. package/dist/lib/platform/verification-sync.js +12 -49
  24. package/dist/lib/sandbox/capture.js +1 -1
  25. package/dist/lib/sandbox/commands/create.js +46 -1
  26. package/dist/lib/sandbox/commands/enter.js +1 -1
  27. package/dist/lib/sandbox/commands/start.js +1 -1
  28. package/dist/lib/sandbox/config.js +10 -0
  29. package/dist/lib/sandbox/control/client.js +4 -1
  30. package/dist/lib/sandbox/control/executor.js +12 -4
  31. package/dist/lib/sandbox/control/lifecycle.js +18 -13
  32. package/dist/lib/sandbox/control/protocol.js +2 -1
  33. package/dist/lib/sandbox/removal.js +15 -8
  34. package/dist/lib/sandbox/task-resolver.js +62 -2
  35. package/dist/lib/task/activity-intent.js +1 -2
  36. package/dist/lib/task/artifact-operations.js +36 -2
  37. package/dist/lib/task/artifact-schema.js +7 -7
  38. package/dist/lib/task/control-authority.js +9 -166
  39. package/dist/lib/task/decision-intents.js +2 -4
  40. package/dist/lib/task/events.js +7 -28
  41. package/dist/lib/task/finalization-handoff.js +92 -0
  42. package/dist/lib/task/finalization.js +123 -12
  43. package/dist/lib/task/intermediate-cleanup.js +9 -148
  44. package/dist/lib/task/ledger-intents.js +3 -6
  45. package/dist/lib/task/lifecycle.js +50 -11
  46. package/dist/lib/task/local-artifact-finalization.js +13 -28
  47. package/dist/lib/task/resolve-ref.js +3 -3
  48. package/dist/lib/task/review-command.js +1 -2
  49. package/dist/lib/task/review-finalization.js +1 -2
  50. package/dist/lib/task/snapshot.js +6 -6
  51. package/dist/lib/task/verification-engine.js +16 -11
  52. package/dist/lib/task/verification.js +1 -13
  53. package/dist/lib/task/workflow-warning-intents.js +2 -4
  54. package/dist/lib/task/write.js +122 -3
  55. package/dist/lifecycle-build-manifest.json +3 -3
  56. package/lib/decide.ts +5 -35
  57. package/lib/git/branch-worktree.ts +41 -0
  58. package/lib/internal/cli-route-inventory.ts +0 -2
  59. package/lib/internal/task-activity.ts +6 -34
  60. package/lib/internal/task-event.ts +4 -37
  61. package/lib/internal/task-finalization.ts +9 -2
  62. package/lib/internal/task-ledger.ts +5 -38
  63. package/lib/internal/task-lifecycle.ts +2 -2
  64. package/lib/internal/task-operation-registry.ts +1 -4
  65. package/lib/internal/task-review.ts +5 -31
  66. package/lib/internal/task-verify.ts +3 -39
  67. package/lib/internal/task-warning.ts +3 -32
  68. package/lib/merge.ts +59 -10
  69. package/lib/platform/github-provider.ts +11 -18
  70. package/lib/platform/github-release-notes.ts +89 -14
  71. package/lib/platform/none-provider.ts +1 -1
  72. package/lib/platform/pr-summary.ts +4 -25
  73. package/lib/platform/provider-contract.ts +38 -12
  74. package/lib/platform/provider-validation.ts +83 -12
  75. package/lib/platform/release-notes.ts +11 -12
  76. package/lib/platform/verification-sync.ts +13 -53
  77. package/lib/sandbox/capture.ts +1 -1
  78. package/lib/sandbox/commands/create.ts +52 -1
  79. package/lib/sandbox/commands/enter.ts +1 -1
  80. package/lib/sandbox/commands/start.ts +1 -1
  81. package/lib/sandbox/config.ts +12 -0
  82. package/lib/sandbox/control/client.ts +5 -1
  83. package/lib/sandbox/control/executor.ts +13 -4
  84. package/lib/sandbox/control/lifecycle.ts +22 -13
  85. package/lib/sandbox/control/protocol.ts +3 -2
  86. package/lib/sandbox/removal.ts +23 -7
  87. package/lib/sandbox/task-resolver.ts +51 -2
  88. package/lib/task/activity-intent.ts +1 -2
  89. package/lib/task/artifact-operations.ts +58 -2
  90. package/lib/task/artifact-schema.ts +9 -7
  91. package/lib/task/control-authority.ts +14 -188
  92. package/lib/task/decision-intents.ts +2 -4
  93. package/lib/task/events.ts +8 -27
  94. package/lib/task/finalization-handoff.ts +104 -0
  95. package/lib/task/finalization.ts +120 -12
  96. package/lib/task/intermediate-cleanup.ts +9 -125
  97. package/lib/task/ledger-intents.ts +3 -6
  98. package/lib/task/lifecycle.ts +45 -13
  99. package/lib/task/local-artifact-finalization.ts +16 -35
  100. package/lib/task/resolve-ref.ts +3 -3
  101. package/lib/task/review-command.ts +2 -5
  102. package/lib/task/review-finalization.ts +1 -4
  103. package/lib/task/snapshot.ts +6 -6
  104. package/lib/task/verification-engine.ts +20 -10
  105. package/lib/task/verification.ts +1 -15
  106. package/lib/task/workflow-warning-intents.ts +2 -4
  107. package/lib/task/write.ts +116 -5
  108. package/package.json +1 -1
  109. package/templates/.agents/rules/evidence-reporting.en.md +1 -2
  110. package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
  111. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
  112. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
  113. package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
  114. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
  115. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
  116. package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
  117. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
  118. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  119. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  120. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  121. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  122. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  123. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  124. package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
  125. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
  126. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  127. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  128. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  129. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  130. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  131. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  132. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  133. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  134. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  135. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  136. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  137. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  138. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  139. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  140. package/dist/lib/internal/task-override.js +0 -138
  141. package/dist/lib/task/artifact-recovery.js +0 -476
  142. package/dist/lib/task/artifact-repair-intent.js +0 -181
  143. package/dist/lib/task/guard-override.js +0 -31
  144. package/dist/lib/task/human-override.js +0 -1259
  145. package/lib/internal/task-override.ts +0 -144
  146. package/lib/task/artifact-recovery.ts +0 -572
  147. package/lib/task/artifact-repair-intent.ts +0 -227
  148. package/lib/task/guard-override.ts +0 -50
  149. package/lib/task/human-override.ts +0 -1522
@@ -1,10 +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';
5
+ import { parseLifecyclePathDecision } from './lifecycle-path.js';
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
- import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
7
+ import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
8
8
  import { getArtifactSchema } from './artifact-schema.js';
9
9
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
10
10
  analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
@@ -16,45 +16,30 @@ function localDiagnosticCode(code) {
16
16
  ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
17
17
  ARTIFACT_MISSING_SECTION: 'LOCAL_ARTIFACT_MISSING_SECTION',
18
18
  ARTIFACT_DUPLICATE_SECTION: 'LOCAL_ARTIFACT_DUPLICATE_SECTION',
19
- 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'
20
22
  };
21
23
  return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
22
24
  }
23
25
  function localDiagnostic(item) {
24
26
  return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
25
27
  }
26
- function isStatusPattern(pattern) {
27
- return pattern === '^\\$ ';
28
- }
29
28
  function validateLocalArtifact(content, options) {
30
29
  const schema = getArtifactSchema(options.family);
31
30
  const diagnostics = [];
32
- const scanned = scanVisibleMarkdown(content);
33
- const structure = inspectArtifactStructure(content, schema);
34
- for (const item of structure.diagnostics)
31
+ const inspection = inspectArtifactContract(content, schema, { scopeStateCheckSection: true });
32
+ for (const item of inspection.diagnostics)
35
33
  diagnostics.push(localDiagnostic(item));
36
- const statusSection = schema.sections.find((section) => section.id === 'state-check');
37
- if (statusSection) {
38
- 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}:`))));
39
- if (statusHeading) {
40
- const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
41
- const body = content.slice(statusHeading.end, next?.start ?? content.length);
42
- for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
43
- if (!new RegExp(pattern, 'm').test(body)) {
44
- 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 });
45
- }
46
- }
47
- }
48
- }
49
- const patternInspection = inspectArtifactPatterns(content, {
50
- ...schema,
51
- requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
52
- });
53
- for (const item of patternInspection.diagnostics)
54
- diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
55
34
  const decisionDetails = inspectDecisionDetailDuplicates(content);
56
35
  if (!decisionDetails.ok)
57
36
  diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.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
+ }
42
+ }
58
43
  return {
59
44
  ok: diagnostics.length === 0,
60
45
  family: options.family,
@@ -41,7 +41,7 @@ function listSortedNumeric(dir, width) {
41
41
  .reverse();
42
42
  }
43
43
  function findInArchive(repoRoot, taskId) {
44
- // archive-tasks SKILL writes to .agents/workspace/archive/YYYY/MM/DD/{taskId}/task.md
44
+ // archive-tasks stores local material at .agents/workspace/archive/YYYY/MM/DD/{taskId}/local/task.md
45
45
  // where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
46
46
  // the task id's creation date. So we cannot derive the path from taskId alone;
47
47
  // walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
@@ -51,7 +51,7 @@ function findInArchive(repoRoot, taskId) {
51
51
  for (const month of listSortedNumeric(yearDir, 2)) {
52
52
  const monthDir = path.join(yearDir, month);
53
53
  for (const day of listSortedNumeric(monthDir, 2)) {
54
- const candidate = path.join(monthDir, day, taskId, 'task.md');
54
+ const candidate = path.join(monthDir, day, taskId, 'local', 'task.md');
55
55
  if (fs.existsSync(candidate))
56
56
  return candidate;
57
57
  }
@@ -125,7 +125,7 @@ function enumerateAllTaskDirs(repoRoot) {
125
125
  for (const day of listSortedNumeric(monthDir, 2).reverse()) {
126
126
  const dayDir = path.join(monthDir, day);
127
127
  for (const taskId of fs.readdirSync(dayDir).sort())
128
- add(taskId, path.join(dayDir, taskId), 'archive');
128
+ add(taskId, path.join(dayDir, taskId, 'local'), 'archive');
129
129
  }
130
130
  }
131
131
  }
@@ -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,7 +9,6 @@ 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
14
  const STAGES = {
@@ -138,7 +137,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
138
137
  taskId = resolved.taskId ?? null;
139
138
  if (!resolved.ok)
140
139
  return reject(resolved.code, resolved.message);
141
- if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
140
+ if (resolved.state !== 'active') {
142
141
  return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
143
142
  }
144
143
  const parsed = parseArtifactName(request.artifact);
@@ -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,
@@ -11,6 +11,7 @@ import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
11
11
  import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
12
12
  import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
13
13
  import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
14
+ import { resolveBranchWorktree } from "../git/branch-worktree.js";
14
15
  import { parseTypedTaskFrontmatter } from "./frontmatter.js";
15
16
  import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
16
17
  import { isValidAgentInfraVersion } from "../version.js";
@@ -483,6 +484,18 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }) {
483
484
  return failResult("artifact", `${path.basename(artifactPath)} is missing required pattern: ${pattern}`);
484
485
  }
485
486
  }
487
+ const stateCheckPatterns = config.state_check_patterns || [];
488
+ if (stateCheckPatterns.length > 0) {
489
+ const scanned = scanVisibleMarkdown(content);
490
+ const stateCheckHeading = scanned.headings.find((heading) => (heading.level === 2 && (heading.text === "状态核对" || heading.text === "State Check")));
491
+ const nextHeading = stateCheckHeading && scanned.headings.find((heading) => (heading.start > stateCheckHeading.start && heading.level <= 2));
492
+ const stateCheckBody = stateCheckHeading ? content.slice(stateCheckHeading.end, nextHeading?.start ?? content.length) : "";
493
+ for (const pattern of stateCheckPatterns) {
494
+ if (!new RegExp(pattern, "m").test(stateCheckBody)) {
495
+ return failResult("artifact", `${path.basename(artifactPath)} state-check section is missing required command: ${pattern}`);
496
+ }
497
+ }
498
+ }
486
499
  return passResult("artifact", `${path.basename(artifactPath)} passed (${requiredSections.length} sections)`);
487
500
  }
488
501
  function checkDecisionDetails({ taskDir, artifactFile }) {
@@ -1078,17 +1091,9 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
1078
1091
  function reviewWorktreeForTask(repositoryRoot, branch) {
1079
1092
  if (!branch)
1080
1093
  return null;
1081
- const expectedRef = `refs/heads/${branch}`;
1082
- const records = execFileSync("git", ["-C", repositoryRoot, "worktree", "list", "--porcelain"], {
1083
- encoding: "utf8",
1084
- stdio: ["pipe", "pipe", "pipe"]
1085
- }).trim().split(/\r?\n\r?\n/);
1086
- for (const record of records) {
1087
- const worktree = /^worktree (.+)$/m.exec(record)?.[1];
1088
- const branchRef = /^branch (.+)$/m.exec(record)?.[1];
1089
- if (worktree && branchRef === expectedRef)
1090
- return worktree;
1091
- }
1094
+ const worktree = resolveBranchWorktree(repositoryRoot, branch);
1095
+ if (worktree)
1096
+ return worktree;
1092
1097
  throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
1093
1098
  }
1094
1099
  function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
@@ -4,7 +4,6 @@ import path from 'node:path';
4
4
  import { verifyInProcess } from './verification-engine.js';
5
5
  import { parseArtifactName } from './artifact-name.js';
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
- import { allowsManualOverride } from './guard-override.js';
8
7
  const gate = (skill, expectedState, artifactFamily) => ({
9
8
  skill, expectedState, mode: 'gate', ...(artifactFamily ? { artifactFamily } : {})
10
9
  });
@@ -50,7 +49,7 @@ async function verifyTaskEvent(request, options = {}) {
50
49
  if (!resolved.ok)
51
50
  return failure(request, resolved.code, resolved.message, { taskId: resolved.taskId, skill: spec.skill, mode: spec.mode });
52
51
  const identity = { taskId: resolved.taskId, taskDir: resolved.taskDir, taskState: resolved.state, skill: spec.skill, mode: spec.mode };
53
- if (resolved.state !== spec.expectedState && !allowsManualOverride(options.manualOverride, 'task-verify', 'VERIFY_TASK_STATE_MISMATCH')) {
52
+ if (resolved.state !== spec.expectedState) {
54
53
  return failure(request, 'VERIFY_TASK_STATE_MISMATCH', `event '${request.event}' requires workspace '${spec.expectedState}', received '${resolved.state}'`, identity);
55
54
  }
56
55
  if (spec.artifactFamily) {
@@ -69,23 +68,12 @@ async function verifyTaskEvent(request, options = {}) {
69
68
  if (spec.mode === 'gate') {
70
69
  const payload = await engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
71
70
  const status = payload.gate;
72
- if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
73
- invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
74
- return {
75
- status: 'pass', changed: false, event: request.event, requestRef: request.taskRef,
76
- ...identity, artifact: request.artifact ?? null, invocations, error: null
77
- };
78
- }
79
71
  invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
80
72
  }
81
73
  else {
82
74
  for (const check of spec.checks ?? []) {
83
75
  const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
84
76
  const status = (payload.effectiveStatus ?? payload.status);
85
- if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
86
- invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
87
- break;
88
- }
89
77
  invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
90
78
  if (status !== 'pass')
91
79
  break;
@@ -3,7 +3,6 @@ import { resolveTaskRef } from './resolve-ref.js';
3
3
  import { parseTaskFrontmatter } from './frontmatter.js';
4
4
  import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
5
5
  import { captureTaskWriteMetadata, writeTask } from './write.js';
6
- import { allowsManualOverride } from './guard-override.js';
7
6
  import { retryHintForWarning } from './operation-outcome.js';
8
7
  function failed(intent, code, message, taskId = null, entityId = null) {
9
8
  return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
@@ -44,8 +43,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
44
43
  const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
45
44
  if (!resolved.ok)
46
45
  return failed(intent, resolved.code, resolved.message, resolved.taskId);
47
- const stateOverride = allowsManualOverride(options.manualOverride, 'workflow-warning', 'TASK_STATE_MISMATCH');
48
- if (resolved.state !== 'active' && !stateOverride)
46
+ if (resolved.state !== 'active')
49
47
  return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
50
48
  let content;
51
49
  let rows;
@@ -102,7 +100,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
102
100
  after = { ...before, status: intent.status, resolvedAt: metadata.timestamp, resolution };
103
101
  }
104
102
  const writeResult = writeTask({
105
- taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
103
+ taskRef: intent.taskRef, expectedState: 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
106
104
  }, {
107
105
  ...options,
108
106
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import crypto from 'node:crypto';
3
4
  import { isValidAgentInfraVersion, VERSION } from '../version.js';
4
5
  import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
5
6
  import { resolveTaskRef } from './resolve-ref.js';
6
- import { allowsManualOverride } from './guard-override.js';
7
7
  import { mutateTableRow, upsertSection } from './sections.js';
8
8
  import { validateCurrentTaskContract } from './current-contract.js';
9
9
  import { invalidationBlocks, parseInvalidationDocument } from './invalidation.js';
@@ -54,7 +54,7 @@ function errorDetails(error, fallback) {
54
54
  message: error instanceof Error ? error.message : String(error)
55
55
  };
56
56
  }
57
- function writeTask(request, options = {}) {
57
+ function writeTaskCore(request, options = {}) {
58
58
  const resolved = options.taskLocation
59
59
  ? {
60
60
  ok: true,
@@ -73,7 +73,7 @@ function writeTask(request, options = {}) {
73
73
  taskMdPath: resolved.taskMdPath,
74
74
  actualState: resolved.state
75
75
  };
76
- if (resolved.state !== request.expectedState && !allowsManualOverride(options.manualOverride, 'task.write', 'TASK_STATE_MISMATCH')) {
76
+ if (resolved.state !== request.expectedState) {
77
77
  return failure(request, identity, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected ${request.expectedState}`);
78
78
  }
79
79
  const io = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
@@ -259,5 +259,124 @@ function writeTask(request, options = {}) {
259
259
  }
260
260
  return { ...successBase, status: 'applied', changed: true };
261
261
  }
262
+ function archiveContentsHash(localDir) {
263
+ const files = [];
264
+ const walk = (dir) => {
265
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
266
+ if (entry.name.includes('\n') || entry.name.includes('\r') || entry.name === 'manifest.md') {
267
+ throw new Error(`Archive source path is not valid: ${path.join(dir, entry.name)}`);
268
+ }
269
+ const target = path.join(dir, entry.name);
270
+ const stat = fs.lstatSync(target);
271
+ if (stat.isSymbolicLink())
272
+ throw new Error(`Archive source cannot contain symbolic links: ${target}`);
273
+ if (stat.isDirectory())
274
+ walk(target);
275
+ else if (stat.isFile()) {
276
+ if (entry.name !== 'contents.sha256')
277
+ files.push(target);
278
+ }
279
+ else
280
+ throw new Error(`Archive source cannot contain special files: ${target}`);
281
+ }
282
+ };
283
+ walk(localDir);
284
+ const rows = files
285
+ .map((file) => ({ file, relative: path.relative(localDir, file).split(path.sep).join('/') }))
286
+ .sort((left, right) => Buffer.compare(Buffer.from(left.relative), Buffer.from(right.relative)))
287
+ .map(({ file, relative }) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${relative}`);
288
+ return `${rows.join('\n')}${rows.length ? '\n' : ''}`;
289
+ }
290
+ function writeArchiveContentsHash(localDir) {
291
+ const contents = archiveContentsHash(localDir);
292
+ const hashPath = path.join(localDir, 'contents.sha256');
293
+ const tempPath = path.join(localDir, `.contents.sha256.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
294
+ try {
295
+ fs.writeFileSync(tempPath, contents, { flag: 'wx' });
296
+ fs.renameSync(tempPath, hashPath);
297
+ }
298
+ catch (error) {
299
+ try {
300
+ fs.unlinkSync(tempPath);
301
+ }
302
+ catch (cleanupError) {
303
+ if (cleanupError.code !== 'ENOENT') {
304
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
305
+ }
306
+ }
307
+ throw error;
308
+ }
309
+ }
310
+ function restoreArchivedTask(taskMdPath, contents, mode) {
311
+ const tempPath = `${taskMdPath}.${process.pid}.${Math.random().toString(36).slice(2)}.rollback`;
312
+ fs.writeFileSync(tempPath, contents, { flag: 'wx', mode });
313
+ try {
314
+ fs.renameSync(tempPath, taskMdPath);
315
+ }
316
+ catch (error) {
317
+ try {
318
+ fs.unlinkSync(tempPath);
319
+ }
320
+ catch (cleanupError) {
321
+ if (cleanupError.code !== 'ENOENT') {
322
+ throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
323
+ }
324
+ }
325
+ throw error;
326
+ }
327
+ }
328
+ function writeTask(request, options = {}) {
329
+ const resolved = options.taskLocation
330
+ ? {
331
+ ok: true,
332
+ repoRoot: options.taskLocation.repoRoot,
333
+ taskId: options.taskLocation.taskId,
334
+ taskDir: path.dirname(options.taskLocation.taskMdPath),
335
+ taskMdPath: options.taskLocation.taskMdPath,
336
+ state: options.taskLocation.state
337
+ }
338
+ : resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
339
+ if (!resolved.ok) {
340
+ return failure(request, { taskId: resolved.taskId, taskMdPath: null, actualState: null }, resolved.code, resolved.message);
341
+ }
342
+ if (resolved.state !== 'archive' || request.expectedState !== 'archive' || request.dryRun) {
343
+ return writeTaskCore(request, { ...options, taskLocation: {
344
+ repoRoot: resolved.repoRoot,
345
+ taskId: resolved.taskId,
346
+ taskMdPath: resolved.taskMdPath,
347
+ state: resolved.state
348
+ } });
349
+ }
350
+ const identity = { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, actualState: resolved.state };
351
+ try {
352
+ const originalTask = fs.readFileSync(resolved.taskMdPath);
353
+ const originalMode = fs.statSync(resolved.taskMdPath).mode;
354
+ archiveContentsHash(resolved.taskDir);
355
+ const result = writeTaskCore(request, { ...options, taskLocation: {
356
+ repoRoot: resolved.repoRoot,
357
+ taskId: resolved.taskId,
358
+ taskMdPath: resolved.taskMdPath,
359
+ state: resolved.state
360
+ } });
361
+ if (result.status === 'applied') {
362
+ try {
363
+ writeArchiveContentsHash(resolved.taskDir);
364
+ }
365
+ catch (error) {
366
+ try {
367
+ restoreArchivedTask(resolved.taskMdPath, originalTask, originalMode);
368
+ }
369
+ catch (rollbackError) {
370
+ throw new Error(`Archive checksum update failed: ${error instanceof Error ? error.message : String(error)}; task rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
371
+ }
372
+ throw error;
373
+ }
374
+ }
375
+ return result;
376
+ }
377
+ catch (error) {
378
+ return failure(request, identity, 'TASK_READ_FAILED', error instanceof Error ? error.message : String(error));
379
+ }
380
+ }
262
381
  export { writeTask, captureTaskWriteMetadata, canonicalTimestamp };
263
382
  //# sourceMappingURL=write.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.4",
4
- "sourceInputHash": "46c0a39f354bff8c80fe19354cdda89a91a4acad060b1f134b6d599bc0ba6806",
5
- "internalExecutableBuildHash": "1bc9e5755c857ee9f13a702452b2ce7c85da94444dc5a8d1305ae80d5899b001"
3
+ "packageVersion": "0.11.6",
4
+ "sourceInputHash": "402048e73650c5ac4edfb1bc4b48ca3fceb7b9068790b1321281c4994121e80c",
5
+ "internalExecutableBuildHash": "47ecd04ac30183443b2b86f83d1668b8bdf9e59162cc9e2cedd333d3011843e9"
6
6
  }
package/lib/decide.ts CHANGED
@@ -2,7 +2,6 @@ import { VERSION } from './version.ts';
2
2
  import { parseTaskScope } from './task/command-options.ts';
3
3
  import { applyHumanDecision } from './task/decision-intents.ts';
4
4
  import { canonicalTimestamp } from './task/write.ts';
5
- import { consumeHumanOverride, failureId } from './task/human-override.ts';
6
5
  import { resolveTaskContext } from './task/resolve-ref.ts';
7
6
  import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.ts';
8
7
 
@@ -12,12 +11,9 @@ type DecideOptions = {
12
11
  version?: string;
13
12
  };
14
13
 
15
- function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined; overrideTicket?: string; overrideTarget?: string; overrideScope?: string } {
14
+ function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined } {
16
15
  const decision: string[] = [];
17
16
  let needsImplementation: boolean | undefined;
18
- let overrideTicket: string | undefined;
19
- let overrideTarget: string | undefined;
20
- let overrideScope: string | undefined;
21
17
  for (let index = 0; index < parts.length; index += 1) {
22
18
  const part = parts[index];
23
19
  if (part === '--needs-implementation') {
@@ -27,18 +23,14 @@ function parseDecisionParts(parts: string[]): { decision: string; needsImplement
27
23
  throw new Error("--needs-implementation must be 'true' or 'false'");
28
24
  }
29
25
  needsImplementation = value === 'true';
30
- } else if (part === '--override-ticket' || part === '--override-target' || part === '--override-scope') {
31
- const value = parts[++index];
32
- if (!value) throw new Error(`${part} requires a value`);
33
- if (part === '--override-ticket') overrideTicket = value;
34
- else if (part === '--override-target') overrideTarget = value;
35
- else overrideScope = value;
26
+ } else if (part?.startsWith('--')) {
27
+ throw new Error(`unknown option '${part}'`);
36
28
  } else {
37
29
  decision.push(parts[index]!);
38
30
  }
39
31
  }
40
32
  if (decision.length === 0) throw new Error('decision content is required');
41
- return { decision: decision.join(' '), needsImplementation, overrideTicket, overrideTarget, overrideScope };
33
+ return { decision: decision.join(' '), needsImplementation };
42
34
  }
43
35
 
44
36
  export async function decide(args: string[], options: DecideOptions = {}): Promise<number> {
@@ -79,29 +71,7 @@ export async function decide(args: string[], options: DecideOptions = {}): Promi
79
71
  metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
80
72
  };
81
73
  let result;
82
- const execute = async () => {
83
- let current = applyHumanDecision(request, writeOptions);
84
- if (current.status !== 'failed' || !parsedDecision.overrideTicket) return current;
85
- if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope) throw new Error('override ticket requires --override-target and --override-scope');
86
- const consumed = await consumeHumanOverride({
87
- taskRef: resolved.taskId,
88
- ticketId: parsedDecision.overrideTicket,
89
- failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
90
- target: parsedDecision.overrideTarget,
91
- scope: parsedDecision.overrideScope
92
- }, {
93
- ...writeOptions,
94
- effectExecutor: (capability) => {
95
- const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
96
- current = retried;
97
- return retried.status === 'failed' || retried.status === 'planned'
98
- ? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
99
- : null;
100
- }
101
- });
102
- if (consumed.status === 'failed') throw new Error(consumed.error.message);
103
- return current;
104
- };
74
+ const execute = async () => applyHumanDecision(request, writeOptions);
105
75
  result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute);
106
76
  if (result.error) throw new Error(result.error.message);
107
77
  return 0;
@@ -0,0 +1,41 @@
1
+ import { execFileSync } from 'node:child_process';
2
+
3
+ function gitText(repositoryRoot: string, args: string[]): string | null {
4
+ try {
5
+ return execFileSync('git', ['-C', repositoryRoot, ...args], {
6
+ encoding: 'utf8',
7
+ stdio: ['ignore', 'pipe', 'pipe']
8
+ }).trim();
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+
14
+ function verifiedWorktreeRoot(candidate: string, expectedBranch: string, expectedCommonDirectory: string): string | null {
15
+ const commonDirectory = gitText(candidate, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
16
+ if (commonDirectory !== expectedCommonDirectory) return null;
17
+ const root = gitText(candidate, ['rev-parse', '--show-toplevel']);
18
+ if (!root) return null;
19
+ const branch = gitText(root, ['symbolic-ref', '--short', 'HEAD']);
20
+ return branch === expectedBranch ? root : null;
21
+ }
22
+
23
+ export function resolveBranchWorktree(repositoryRoot: string, expectedBranch: string): string | null {
24
+ const commonDirectory = gitText(repositoryRoot, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
25
+ if (!commonDirectory) return null;
26
+ const currentRoot = verifiedWorktreeRoot(repositoryRoot, expectedBranch, commonDirectory);
27
+ if (currentRoot) return currentRoot;
28
+
29
+ const records = gitText(repositoryRoot, ['worktree', 'list', '--porcelain']);
30
+ if (!records) return null;
31
+ const expectedRef = `refs/heads/${expectedBranch}`;
32
+ for (const record of records.split(/\r?\n\r?\n/)) {
33
+ const candidate = /^worktree (.+)$/m.exec(record)?.[1];
34
+ const branch = /^branch (.+)$/m.exec(record)?.[1];
35
+ if (candidate && branch === expectedRef) {
36
+ const verifiedRoot = verifiedWorktreeRoot(candidate, expectedBranch, commonDirectory);
37
+ if (verifiedRoot) return verifiedRoot;
38
+ }
39
+ }
40
+ return null;
41
+ }
@@ -61,7 +61,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
61
61
  'task-review': ['preflight', 'finalize-summary'],
62
62
  'task-event': ['event'],
63
63
  'task-invalidation': ['reconcile'],
64
- 'task-override': ['diagnose', 'issue', 'consume'],
65
64
  'task-lifecycle': ['intent'],
66
65
  'task-finalization': ['complete'],
67
66
  'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
@@ -98,7 +97,6 @@ export function internalRouteSelector(command: string, args: readonly string[]):
98
97
  if (command === 'task-orchestration') return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
99
98
  if (command === 'task-review') return args[1] ?? '';
100
99
  if (command === 'task-invalidation') return args[1] ?? '';
101
- if (command === 'task-override') return args[1] ?? '';
102
100
  if (command === 'task-artifact') return args[1] ?? '';
103
101
  if (command === 'platform-pr-review' && first(args) === 'publish') {
104
102
  const scope = optionValue(args, '--scope') ?? '';