@cassiomc1/forgeloop 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/.github/copilot-instructions.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/AGENT_COMPATIBILITY.md +8 -0
  4. package/CLAUDE.md +1 -1
  5. package/CONTRIBUTING.md +90 -0
  6. package/DOCS_INDEX.md +46 -12
  7. package/ENG/c-development-eng.md +112 -0
  8. package/ENG/cpp-development-eng.md +109 -0
  9. package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
  10. package/ENG/go-development-eng.md +103 -0
  11. package/ENG/java-development-eng.md +125 -0
  12. package/ENG/nodejs-backend-development-eng.md +605 -0
  13. package/ENG/php-development-eng.md +104 -0
  14. package/ENG/rust-development-eng.md +422 -0
  15. package/ENG/sec-code-eng.md +7 -7
  16. package/ENG/sql-development-eng.md +108 -0
  17. package/ENG/swift-development-eng.md +111 -0
  18. package/ENG/typescript-development-eng.md +108 -0
  19. package/EXECUTION_STATE.md +12 -0
  20. package/GUIDE_ROUTER.md +418 -9
  21. package/LOOP_ENGINEERING.md +28 -2
  22. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  23. package/PROTOCOL_INTEGRATION.md +55 -2
  24. package/QUALITY_SCORECARD.md +1 -0
  25. package/README.md +78 -52
  26. package/TERMINOLOGY.md +2 -0
  27. package/THIRD_PARTY_NOTICES.md +19 -7
  28. package/THREAT_MODEL.md +140 -1
  29. package/completions/_forgeloop +22 -4
  30. package/completions/forgeloop.bash +40 -4
  31. package/completions/forgeloop.fish +130 -1
  32. package/docs/ADVISORY_CONTEXT.md +25 -0
  33. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  34. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  35. package/docs/AGENT_PROTOCOL_SUMMARY.md +81 -3
  36. package/docs/AGENT_SKILL.md +66 -0
  37. package/docs/ARTIFACT_REFERENCE.md +123 -0
  38. package/docs/AUDIT_UX.md +46 -0
  39. package/docs/BROWSER_VERIFICATION.md +136 -0
  40. package/docs/CLI_REFERENCE.md +392 -10
  41. package/docs/CODE_ATTESTATION.md +2 -2
  42. package/docs/DOCUMENTATION_GUIDE.md +34 -12
  43. package/docs/GETTING_STARTED.md +59 -0
  44. package/docs/JEV_BENCHMARKS.md +31 -0
  45. package/docs/MODEL_ROUTING.md +37 -0
  46. package/docs/OPENSRC_ADAPTER.md +241 -0
  47. package/docs/PACKAGE_CONTENTS.md +60 -19
  48. package/docs/PROVIDERS.md +126 -0
  49. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  50. package/docs/RECIPES.md +32 -0
  51. package/docs/RELEASE_CHECKLIST.md +66 -5
  52. package/docs/SECURITY_REVIEW.md +71 -0
  53. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  54. package/docs/TEST_INTELLIGENCE.md +29 -0
  55. package/docs/TEST_PRUNING.md +14 -0
  56. package/docs/TROUBLESHOOTING.md +298 -3
  57. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  58. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  59. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  60. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  61. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  62. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  63. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  64. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  65. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  66. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  67. package/docs/diagrams/README.md +13 -9
  68. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  69. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  70. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  71. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  72. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  73. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  74. package/docs/documentation-manifest.json +1397 -0
  75. package/docs/protocol-requirements.json +101 -0
  76. package/package.json +46 -4
  77. package/schemas/config.schema.json +14 -0
  78. package/schemas/context-plan.schema.json +18 -0
  79. package/schemas/routing-input.schema.json +1 -1
  80. package/schemas/semantic-decision.schema.json +46 -0
  81. package/schemas/test-utility.schema.json +44 -0
  82. package/scripts/CI_VALIDATORS.md +84 -11
  83. package/scripts/benchmark-jev.mjs +5 -0
  84. package/scripts/benchmark-test-intelligence.mjs +4 -0
  85. package/scripts/generate-agent-protocol-summary.mjs +40 -1
  86. package/scripts/generate-forgeloop-skill.mjs +133 -0
  87. package/scripts/jev-smoke.mjs +19 -0
  88. package/skills/forgeloop/README.md +9 -0
  89. package/skills/forgeloop/SKILL.md +77 -0
  90. package/skills/forgeloop/references/lifecycle.md +9 -0
  91. package/skills/forgeloop/references/recovery.md +7 -0
  92. package/skills/forgeloop/references/verification.md +7 -0
  93. package/src/adapters/agent-browser/assertions.js +47 -0
  94. package/src/adapters/agent-browser/commands.js +54 -0
  95. package/src/adapters/agent-browser/index.js +3 -0
  96. package/src/adapters/agent-browser/locator.js +40 -0
  97. package/src/adapters/agent-browser/process.js +215 -0
  98. package/src/adapters/agent-browser/provider.js +313 -0
  99. package/src/adapters/emulated-services/constants.js +24 -0
  100. package/src/adapters/emulated-services/index.js +7 -0
  101. package/src/adapters/emulated-services/process.js +162 -0
  102. package/src/adapters/emulated-services/provider.js +282 -0
  103. package/src/adapters/opensrc/normalize.js +90 -0
  104. package/src/adapters/opensrc/process.js +248 -0
  105. package/src/adapters/opensrc/provider.js +338 -0
  106. package/src/adapters/opensrc/search.js +264 -0
  107. package/src/adapters/typesafe/client.js +28 -0
  108. package/src/adapters/typesafe/engine.js +63 -0
  109. package/src/adapters/typesafe/normalize.js +41 -0
  110. package/src/cli.js +108 -0
  111. package/src/commands/checkpoint-revalidate.js +176 -0
  112. package/src/commands/context-plan.js +38 -0
  113. package/src/commands/contract-create.js +264 -0
  114. package/src/commands/contract-revise.js +236 -0
  115. package/src/commands/decision-show.js +14 -0
  116. package/src/commands/decision-status.js +22 -0
  117. package/src/commands/discover.js +41 -0
  118. package/src/commands/doctor.js +15 -0
  119. package/src/commands/gate-record.js +205 -0
  120. package/src/commands/gate-revalidate.js +137 -0
  121. package/src/commands/model-route.js +32 -0
  122. package/src/commands/next.js +19 -7
  123. package/src/commands/route.js +146 -18
  124. package/src/commands/semantic-plan.js +17 -0
  125. package/src/commands/task-abandon.js +224 -0
  126. package/src/commands/task-create.js +84 -25
  127. package/src/commands/task-list.js +22 -2
  128. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  129. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  130. package/src/commands/test-inventory.js +5 -0
  131. package/src/commands/test-prune-plan.js +5 -0
  132. package/src/commands/test-prune-probe.js +5 -0
  133. package/src/commands/test-utility.js +5 -0
  134. package/src/commands/validate-protocol.js +10 -1
  135. package/src/config/guides.json +44 -0
  136. package/src/core/artifact-registry.js +24 -0
  137. package/src/core/audit-ux.js +514 -0
  138. package/src/core/browser-verification/constants.js +149 -0
  139. package/src/core/browser-verification/normalize.js +254 -0
  140. package/src/core/browser-verification/provider.js +519 -0
  141. package/src/core/browser-verification/service.js +115 -0
  142. package/src/core/build-script.js +151 -0
  143. package/src/core/c-cpp-project.js +143 -0
  144. package/src/core/checkpoint-revalidation.js +319 -0
  145. package/src/core/cli-command-definitions.js +249 -1
  146. package/src/core/command-executors.js +115 -3
  147. package/src/core/command-input.js +212 -102
  148. package/src/core/completion-artifacts.js +14 -5
  149. package/src/core/completion.js +4 -6
  150. package/src/core/config.js +3 -0
  151. package/src/core/context-compiler/budget.js +9 -0
  152. package/src/core/context-compiler/candidates.js +39 -0
  153. package/src/core/context-compiler/compiler.js +63 -0
  154. package/src/core/context-compiler/fingerprint.js +11 -0
  155. package/src/core/context-compiler/policy.js +13 -0
  156. package/src/core/context-compiler/result.js +23 -0
  157. package/src/core/contract-bootstrap-recovery.js +655 -0
  158. package/src/core/contract-presets.js +82 -0
  159. package/src/core/contract-revision.js +210 -0
  160. package/src/core/decision/artifact.js +69 -0
  161. package/src/core/decision/benchmarks.js +103 -0
  162. package/src/core/decision/cache.js +27 -0
  163. package/src/core/decision/constants.js +58 -0
  164. package/src/core/decision/cutover.js +34 -0
  165. package/src/core/decision/engine.js +22 -0
  166. package/src/core/decision/errors.js +68 -0
  167. package/src/core/decision/events.js +101 -0
  168. package/src/core/decision/freshness.js +19 -0
  169. package/src/core/decision/normalizers/index.js +115 -0
  170. package/src/core/decision/policy.js +18 -0
  171. package/src/core/decision/projection.js +16 -0
  172. package/src/core/decision/question-registry.js +201 -0
  173. package/src/core/decision/request.js +26 -0
  174. package/src/core/decision/resolver.js +130 -0
  175. package/src/core/decision/result.js +58 -0
  176. package/src/core/decision/service.js +156 -0
  177. package/src/core/decision/state-builder.js +65 -0
  178. package/src/core/decision/task-bindings.js +30 -0
  179. package/src/core/decision/test-provider.js +32 -0
  180. package/src/core/decision/thresholds.js +15 -0
  181. package/src/core/error-codes.js +281 -3
  182. package/src/core/events.js +226 -57
  183. package/src/core/evidence-readiness.js +9 -0
  184. package/src/core/execution-prerequisites.js +14 -0
  185. package/src/core/execution-profile.js +63 -38
  186. package/src/core/filesystem.js +1 -10
  187. package/src/core/gate-provenance.js +124 -0
  188. package/src/core/go-project.js +206 -0
  189. package/src/core/integration-invocation-policy.js +27 -4
  190. package/src/core/integration-resources.js +86 -61
  191. package/src/core/java-project.js +403 -0
  192. package/src/core/model-router/constants.js +10 -0
  193. package/src/core/model-router/policy.js +103 -0
  194. package/src/core/model-router/router.js +37 -0
  195. package/src/core/multi-language-project.js +117 -0
  196. package/src/core/next-action-model.js +58 -0
  197. package/src/core/next-action-phases.js +130 -42
  198. package/src/core/next-action-refresh.js +43 -9
  199. package/src/core/next-action-review-phase.js +7 -2
  200. package/src/core/next-action.js +35 -7
  201. package/src/core/next-explanation.js +63 -0
  202. package/src/core/phase.js +128 -10
  203. package/src/core/php-project.js +85 -0
  204. package/src/core/preflight-consistency.js +23 -9
  205. package/src/core/preflight-loaders.js +37 -5
  206. package/src/core/project-detection.js +1760 -52
  207. package/src/core/protocol-info.js +65 -0
  208. package/src/core/protocol.js +20 -0
  209. package/src/core/reconcile-closure.js +132 -53
  210. package/src/core/recovery-history.js +1 -0
  211. package/src/core/resumability.js +154 -44
  212. package/src/core/route-artifact.js +15 -1
  213. package/src/core/router.js +223 -4
  214. package/src/core/runtime-context.js +118 -61
  215. package/src/core/rust-project.js +400 -0
  216. package/src/core/schema-validation.js +3 -0
  217. package/src/core/security-review/constants.js +64 -0
  218. package/src/core/security-review/normalize.js +245 -0
  219. package/src/core/security-review/provider.js +204 -0
  220. package/src/core/security-review/service.js +134 -0
  221. package/src/core/semantic-planning/constants.js +19 -0
  222. package/src/core/semantic-planning/projection.js +94 -0
  223. package/src/core/semantic-planning/service.js +15 -0
  224. package/src/core/sources.js +37 -0
  225. package/src/core/sql-project.js +141 -0
  226. package/src/core/swift-project.js +200 -0
  227. package/src/core/task-claim-state.js +201 -1
  228. package/src/core/task-conflict-inspection.js +31 -5
  229. package/src/core/task-paths.js +13 -0
  230. package/src/core/task-recovery.js +1 -0
  231. package/src/core/templates.js +3 -0
  232. package/src/core/test-intelligence/benchmarks.js +68 -0
  233. package/src/core/test-intelligence/inventory.js +73 -0
  234. package/src/core/test-intelligence/prune.js +90 -0
  235. package/src/core/test-intelligence/semantic-state.js +15 -0
  236. package/src/core/test-intelligence/service.js +40 -0
  237. package/src/core/test-intelligence/utility.js +50 -0
  238. package/src/core/trace.js +11 -7
  239. package/src/core/transaction.js +1 -0
  240. package/src/core/typescript-project.js +349 -0
  241. package/src/core/xml-structure.js +123 -0
  242. package/src/integration.d.ts +492 -0
  243. package/src/integration.js +54 -0
  244. package/src/providers/README.md +47 -0
  245. package/src/providers/capabilities.js +46 -0
  246. package/src/providers/errors.js +15 -0
  247. package/src/providers/index.js +29 -0
  248. package/src/providers/json-snapshot.js +105 -0
  249. package/src/providers/registry.js +152 -0
@@ -0,0 +1,224 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import { resolveTaskContext } from "../core/task-context.js";
4
+ import { inspectTaskConflictState } from "../core/task-conflict-inspection.js";
5
+ import { appendProtocolEvent } from "../core/events.js";
6
+ import { withTaskTransaction } from "../core/transaction.js";
7
+ import { currentRepositoryFingerprint } from "../core/repository.js";
8
+ import { withProjectClaimsLock } from "../core/task-lock.js";
9
+ import { readTaskDescriptor } from "../core/task-descriptor.js";
10
+ import { createTaskRecovery, writeTaskRecovery } from "../core/task-recovery.js";
11
+ import { resolveTaskClaimState } from "../core/task-claim-state.js";
12
+ import {
13
+ E_TASK_ABANDON_AUTHORIZATION_REQUIRED,
14
+ E_TASK_ABANDON_INCONSISTENT,
15
+ E_TASK_ABANDON_INVALID_STATE,
16
+ E_TASK_ABANDON_UNSAFE,
17
+ E_TASK_ALREADY_ABANDONED,
18
+ } from "../core/error-codes.js";
19
+
20
+ const ABANDONMENT_REASON_CODES = Object.freeze(["CALLER_ABANDONED"]);
21
+
22
+ function abandonError(code, message, details = {}) {
23
+ const error = new Error(message);
24
+ error.code = code;
25
+ Object.assign(error, details);
26
+ return error;
27
+ }
28
+
29
+ function sameList(left, right) {
30
+ return Array.isArray(left)
31
+ && Array.isArray(right)
32
+ && left.length === right.length
33
+ && left.every((value, index) => value === right[index]);
34
+ }
35
+
36
+ function snapshot(inspection) {
37
+ return {
38
+ phase: inspection.evidence.phase,
39
+ revision: inspection.evidence.workStateRevision,
40
+ ledgerLastSeq: inspection.evidence.ledgerLastSeq,
41
+ claimState: inspection.evidence.claimState,
42
+ effectiveWriteClaims: [...inspection.evidence.effectiveWriteClaims],
43
+ };
44
+ }
45
+
46
+ function assertSnapshotUnchanged(before, after, taskId) {
47
+ const unchanged = before.phase === after.phase
48
+ && before.revision === after.revision
49
+ && before.ledgerLastSeq === after.ledgerLastSeq
50
+ && before.claimState === after.claimState
51
+ && sameList(before.effectiveWriteClaims, after.effectiveWriteClaims);
52
+ if (!unchanged) {
53
+ throw abandonError(
54
+ E_TASK_ABANDON_UNSAFE,
55
+ `Task ${taskId} changed during abandonment precondition validation`,
56
+ { before, after },
57
+ );
58
+ }
59
+ }
60
+
61
+ async function assertAbandonableOwnership(target, taskId, packageRoot) {
62
+ const projection = await resolveTaskClaimState(target, { taskId, packageRoot });
63
+ if (projection.phase === "COMPLETE" || projection.claimState === "RELEASED_BY_COMPLETION") {
64
+ throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is COMPLETE and cannot be abandoned`);
65
+ }
66
+ if (!projection.valid || !projection.ownershipValid) {
67
+ throw abandonError(
68
+ E_TASK_ABANDON_INCONSISTENT,
69
+ `Task ${taskId} claim ownership is inconsistent; abandonment is blocked`,
70
+ { reasonCodes: projection.reasonCodes, ownershipErrors: projection.ownershipErrors },
71
+ );
72
+ }
73
+ if (projection.claimState === "RELEASED_BY_RECOVERY") {
74
+ throw abandonError(
75
+ E_TASK_ALREADY_ABANDONED,
76
+ `Task ${taskId} is already abandoned/recovered`,
77
+ { recovery: projection.recovery },
78
+ );
79
+ }
80
+ if (projection.claimState !== "ACTIVE" || projection.mutationAllowed !== true) {
81
+ throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not an active mutable task`);
82
+ }
83
+ return projection;
84
+ }
85
+
86
+ function assertInspectionAbandonable(taskId, inspection) {
87
+ if (!inspection.evidence.healthy || !inspection.evidence.ledgerValid || !inspection.evidence.ownershipValid) {
88
+ throw abandonError(
89
+ E_TASK_ABANDON_INCONSISTENT,
90
+ `Task ${taskId} cannot be abandoned because canonical ownership or ledger validation failed`,
91
+ { reasonCodes: inspection.reasonCodes, ledgerErrors: inspection.evidence.ledgerErrors },
92
+ );
93
+ }
94
+ if (!inspection.evidence.phase || inspection.evidence.phase === "COMPLETE") {
95
+ throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not eligible for abandonment`);
96
+ }
97
+ if (inspection.evidence.claimState !== "ACTIVE" || inspection.evidence.mutationAllowed !== true) {
98
+ throw abandonError(E_TASK_ABANDON_INVALID_STATE, `Task ${taskId} is not an active mutable task`);
99
+ }
100
+ }
101
+
102
+ export async function runTaskAbandon({
103
+ target,
104
+ packageRoot,
105
+ taskId,
106
+ acknowledgeAbandonment = false,
107
+ } = {}) {
108
+ if (typeof taskId !== "string" || taskId.trim() === "") {
109
+ throw abandonError(E_TASK_ABANDON_INVALID_STATE, "task-abandon requires an explicit --task ID");
110
+ }
111
+ if (!acknowledgeAbandonment) {
112
+ throw abandonError(
113
+ E_TASK_ABANDON_AUTHORIZATION_REQUIRED,
114
+ "task-abandon requires explicit caller acknowledgement: re-run with --acknowledge-abandonment",
115
+ );
116
+ }
117
+
118
+ let context;
119
+ try {
120
+ context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
121
+ } catch (error) {
122
+ throw abandonError(
123
+ E_TASK_ABANDON_INCONSISTENT,
124
+ `Task ${taskId} cannot be abandoned because its canonical identity is unreadable`,
125
+ { causeCode: error.code, causeMessage: error.message },
126
+ );
127
+ }
128
+ const effectiveTaskId = context.taskId;
129
+ await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
130
+
131
+ return withProjectClaimsLock(target, "task-abandon", async () => {
132
+ await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
133
+ const inspectionBeforeLock = await inspectTaskConflictState(target, {
134
+ taskId: effectiveTaskId,
135
+ packageRoot,
136
+ });
137
+ assertInspectionAbandonable(effectiveTaskId, inspectionBeforeLock);
138
+ const before = snapshot(inspectionBeforeLock);
139
+
140
+ return withTaskTransaction({
141
+ target,
142
+ taskId: effectiveTaskId,
143
+ operation: "task-abandon",
144
+ packageRoot,
145
+ recordCommitEvent: true,
146
+ }, async (transaction) => {
147
+ const descriptor = await readTaskDescriptor(target, context.taskKey, packageRoot);
148
+ if (descriptor.value.taskId !== effectiveTaskId || descriptor.value.taskKey !== context.taskKey) {
149
+ throw abandonError(E_TASK_ABANDON_UNSAFE, `Task ${effectiveTaskId} identity changed during abandonment`);
150
+ }
151
+
152
+ await assertAbandonableOwnership(target, effectiveTaskId, packageRoot);
153
+ const inspection = await inspectTaskConflictState(target, {
154
+ taskId: effectiveTaskId,
155
+ packageRoot,
156
+ ignoredLockId: transaction.lock.lockId,
157
+ });
158
+ assertInspectionAbandonable(effectiveTaskId, inspection);
159
+ assertSnapshotUnchanged(before, snapshot(inspection), effectiveTaskId);
160
+
161
+ const repository = await currentRepositoryFingerprint(target);
162
+ const recoveryId = `recovery-${randomUUID()}`;
163
+ const abandonedAt = new Date().toISOString();
164
+ const releasedClaims = [...inspection.evidence.effectiveWriteClaims];
165
+ const details = {
166
+ recoveryId,
167
+ classification: "ABANDONED",
168
+ reasonCodes: [...ABANDONMENT_REASON_CODES],
169
+ previousPhase: inspection.evidence.phase,
170
+ previousRevision: inspection.evidence.workStateRevision,
171
+ previousHead: inspection.evidence.repositoryHead,
172
+ previousBranch: inspection.evidence.repositoryBranch,
173
+ currentHead: repository.head,
174
+ currentBranch: repository.branch,
175
+ releasedClaims,
176
+ authorityKind: "CALLER_ACKNOWLEDGED",
177
+ };
178
+
179
+ const abandonmentEvent = await appendProtocolEvent(target, {
180
+ taskId: effectiveTaskId,
181
+ event: "TASK_ABANDONED",
182
+ at: abandonedAt,
183
+ details,
184
+ }, packageRoot, { taskId: effectiveTaskId });
185
+
186
+ await writeTaskRecovery(target, createTaskRecovery({
187
+ taskId: effectiveTaskId,
188
+ recoveredAt: abandonedAt,
189
+ recoveryId,
190
+ recoveryEventSeq: abandonmentEvent.seq,
191
+ classificationAtRecovery: "ABANDONED",
192
+ reasonCodes: ABANDONMENT_REASON_CODES,
193
+ releasedClaims,
194
+ previousPhase: inspection.evidence.phase,
195
+ previousRevision: inspection.evidence.workStateRevision,
196
+ repositoryFingerprint: repository,
197
+ authority: { kind: "CALLER_ACKNOWLEDGED" },
198
+ }), packageRoot);
199
+
200
+ return {
201
+ taskId: effectiveTaskId,
202
+ taskKey: context.taskKey,
203
+ abandoned: true,
204
+ recovered: true,
205
+ recoveryId,
206
+ classification: "ABANDONED",
207
+ reasonCodes: [...ABANDONMENT_REASON_CODES],
208
+ phase: inspection.evidence.phase,
209
+ claimsReleased: true,
210
+ releasedClaims,
211
+ effectiveWriteClaims: [],
212
+ claimState: "RELEASED_BY_RECOVERY",
213
+ mutationAllowed: false,
214
+ authority: { kind: "CALLER_ACKNOWLEDGED" },
215
+ message: `Task ${effectiveTaskId} explicitly abandoned by caller acknowledgement; write claims released without completion claim`,
216
+ };
217
+ });
218
+ });
219
+ }
220
+
221
+ export function formatTaskAbandonResult(result) {
222
+ const claims = result.releasedClaims.length === 0 ? "none" : result.releasedClaims.join(", ");
223
+ return `${result.message}\nclassification: ${result.classification}\nphase: ${result.phase} (unchanged)\nreleased claims: ${claims}\n`;
224
+ }
@@ -9,6 +9,7 @@ import { withTaskTransaction } from "../core/transaction.js";
9
9
  import { taskDirectory } from "../core/task-paths.js";
10
10
  import { ensureWithin, fileExists } from "../core/filesystem.js";
11
11
  import { validateContract, writeContract } from "../core/contract.js";
12
+ import { createPresetContract, CONTRACT_PRESET_IDS } from "../core/contract-presets.js";
12
13
  import { appendProtocolEvent } from "../core/events.js";
13
14
  import { E_TASK_REQUIRED, E_TASK_ALREADY_EXISTS, E_TASK_DESCRIPTOR_INVALID } from "../core/error-codes.js";
14
15
  import { recoveryGuidanceForClassification } from "../core/next-action-model.js";
@@ -56,12 +57,45 @@ export async function assertNoScopeConflictsWithInspection(claims, existingTasks
56
57
  }
57
58
  }
58
59
 
59
- export async function runTaskCreate({ target, packageRoot, taskId, claims = [], contractFile = null } = {}) {
60
+ async function validateTaskCreateScope({ target, packageRoot, claims, taskId }) {
61
+ const allTasks = await discoverTasks(target, packageRoot);
62
+ await assertNoScopeConflictsWithInspection(claims, allTasks, taskId, { target, packageRoot });
63
+ if (claims.length > 0) await assertScopeClean(target, claims);
64
+ }
65
+
66
+ function previewResult({ taskId, normalizedClaims, preset, proposedContract }) {
67
+ return {
68
+ preview: true,
69
+ taskId,
70
+ writeClaims: normalizedClaims,
71
+ preset,
72
+ contract: proposedContract,
73
+ createsLifecycleState: false,
74
+ };
75
+ }
76
+
77
+ export async function runTaskCreate({
78
+ target,
79
+ packageRoot,
80
+ taskId,
81
+ claims = [],
82
+ contractFile = null,
83
+ preset = null,
84
+ preview = false,
85
+ } = {}) {
60
86
  if (!taskId) {
61
87
  throw taskError(E_TASK_REQUIRED, "--task is required for task-create");
62
88
  }
63
89
  assertTaskId(taskId);
64
90
 
91
+ if (preset && !CONTRACT_PRESET_IDS.includes(preset)) {
92
+ const error = new Error(`Unknown contract preset: ${preset}. Expected one of ${CONTRACT_PRESET_IDS.join(", ")}`);
93
+ error.code = "E_CONTRACT_PRESET_UNKNOWN";
94
+ throw error;
95
+ }
96
+ if (preset && contractFile) {
97
+ throw taskError("E_CONTRACT_INPUT_CONFLICT", "Use either --preset or --contract-file, not both");
98
+ }
65
99
  const existing = await findTaskById(target, taskId, packageRoot);
66
100
  if (existing) {
67
101
  throw taskError(E_TASK_ALREADY_EXISTS, `Task already exists: ${taskId}`);
@@ -69,13 +103,45 @@ export async function runTaskCreate({ target, packageRoot, taskId, claims = [],
69
103
 
70
104
  const normalizedClaims = normalizeWriteClaims(claims ?? []);
71
105
 
72
- return withProjectClaimsLock(target, async () => {
73
- const allTasks = await discoverTasks(target, packageRoot);
74
- await assertNoScopeConflictsWithInspection(normalizedClaims, allTasks, taskId, { target, packageRoot });
75
- if (normalizedClaims.length > 0) {
76
- await assertScopeClean(target, normalizedClaims);
106
+ let proposedContract = null;
107
+ if (preset) proposedContract = createPresetContract({ taskId, preset, claims: normalizedClaims });
108
+ if (contractFile) {
109
+ const sourcePath = ensureWithin(target, contractFile);
110
+ if (!(await fileExists(sourcePath))) {
111
+ throw taskError("E_CONTRACT_MISSING", `Specified contract file not found: ${contractFile}`);
112
+ }
113
+ const raw = await readFile(sourcePath, "utf8");
114
+ try {
115
+ proposedContract = JSON.parse(raw);
116
+ } catch {
117
+ throw taskError("E_CONTRACT_INVALID", `Specified contract file is not valid JSON: ${contractFile}`);
77
118
  }
119
+ await validateContract(proposedContract, packageRoot);
120
+ if (proposedContract.taskId && proposedContract.taskId !== taskId) {
121
+ throw taskError(
122
+ E_TASK_DESCRIPTOR_INVALID,
123
+ `Contract taskId "${proposedContract.taskId}" does not match requested taskId "${taskId}"`,
124
+ );
125
+ }
126
+ proposedContract = { ...proposedContract, taskId };
127
+ }
128
+
129
+ if (proposedContract) await validateContract({ ...proposedContract, taskId }, packageRoot);
130
+
131
+ // Preview is advisory: it must never create the claims-lock directory, remove
132
+ // a stale lock, or fail because another process currently owns the lock.
133
+ if (preview) {
134
+ await validateTaskCreateScope({ target, packageRoot, claims: normalizedClaims, taskId });
135
+ return previewResult({ taskId, normalizedClaims, preset, proposedContract });
136
+ }
78
137
 
138
+ return withProjectClaimsLock(target, async () => {
139
+ // Re-check after acquiring the reservation. Preview results are not a
140
+ // reservation and the repository may have changed since they were built.
141
+ if (await findTaskById(target, taskId, packageRoot)) {
142
+ throw taskError(E_TASK_ALREADY_EXISTS, `Task already exists: ${taskId}`);
143
+ }
144
+ await validateTaskCreateScope({ target, packageRoot, claims: normalizedClaims, taskId });
79
145
  return withTaskTransaction({ target, taskId, operation: "task-create", packageRoot, recordCommitEvent: true }, async () => {
80
146
  const descriptor = createTaskDescriptor({
81
147
  taskId,
@@ -84,26 +150,9 @@ export async function runTaskCreate({ target, packageRoot, taskId, claims = [],
84
150
  const written = await writeTaskDescriptor(target, descriptor, packageRoot);
85
151
 
86
152
  let contractCopied = false;
87
- if (contractFile) {
88
- const sourcePath = ensureWithin(target, contractFile);
89
- if (!(await fileExists(sourcePath))) {
90
- throw taskError("E_CONTRACT_MISSING", `Specified contract file not found: ${contractFile}`);
91
- }
92
- const raw = await readFile(sourcePath, "utf8");
93
- let parsed;
94
- try {
95
- parsed = JSON.parse(raw);
96
- } catch {
97
- throw taskError("E_CONTRACT_INVALID", `Specified contract file is not valid JSON: ${contractFile}`);
98
- }
153
+ if (proposedContract) {
154
+ const parsed = { ...proposedContract, taskId };
99
155
  await validateContract(parsed, packageRoot);
100
- if (parsed.taskId && parsed.taskId !== taskId) {
101
- throw taskError(
102
- E_TASK_DESCRIPTOR_INVALID,
103
- `Contract taskId "${parsed.taskId}" does not match requested taskId "${taskId}"`,
104
- );
105
- }
106
- parsed.taskId = taskId;
107
156
  await writeContract(target, parsed, packageRoot, { taskId });
108
157
  contractCopied = true;
109
158
  }
@@ -128,6 +177,16 @@ export async function runTaskCreate({ target, packageRoot, taskId, claims = [],
128
177
  }
129
178
 
130
179
  export function formatTaskCreateResult(result) {
180
+ if (result.preview) {
181
+ return `${JSON.stringify({
182
+ preview: true,
183
+ taskId: result.taskId,
184
+ preset: result.preset,
185
+ writeClaims: result.writeClaims,
186
+ contract: result.contract,
187
+ createsLifecycleState: false,
188
+ }, null, 2)}\n`;
189
+ }
131
190
  const claims = result.writeClaims.length === 0 ? "none" : result.writeClaims.join(", ");
132
191
  return `created task: ${result.taskId}\nkey: ${result.taskKey}\ndirectory: ${result.directory}\nclaims: ${claims}\n`;
133
192
  }
@@ -1,9 +1,23 @@
1
1
  import { discoverTasks } from "../core/task-discovery.js";
2
+ import { WORK_PHASES } from "../core/protocol.js";
2
3
 
3
- export async function runTaskList({ target, packageRoot } = {}) {
4
+ export async function runTaskList({ target, packageRoot, phase = null, active = false, limit = null, offset = 0 } = {}) {
4
5
  const tasks = await discoverTasks(target, packageRoot);
6
+ const normalizedPhase = typeof phase === "string" && phase.trim() !== "" ? phase.trim().toUpperCase() : null;
7
+ if (normalizedPhase && !WORK_PHASES.includes(normalizedPhase)) {
8
+ const error = new Error(`Unknown task phase filter: ${phase}`);
9
+ error.code = "E_TASK_PHASE_INVALID";
10
+ throw error;
11
+ }
12
+ const phaseFiltered = normalizedPhase ? tasks.filter((task) => task.phase === normalizedPhase) : tasks;
13
+ const filtered = active
14
+ ? phaseFiltered.filter((task) => task.claimState === "ACTIVE" && task.mutationAllowed === true)
15
+ : phaseFiltered;
16
+ const start = Number.isInteger(offset) && offset >= 0 ? offset : 0;
17
+ const end = Number.isInteger(limit) && limit >= 0 ? start + limit : undefined;
18
+ const projected = filtered.slice(start, end);
5
19
  return {
6
- tasks: tasks.map((task) => {
20
+ tasks: projected.map((task) => {
7
21
  if (task.healthy === false) {
8
22
  return {
9
23
  taskId: task.taskId ?? null,
@@ -35,6 +49,12 @@ export async function runTaskList({ target, packageRoot } = {}) {
35
49
  updatedAt: task.updatedAt,
36
50
  };
37
51
  }),
52
+ ...(normalizedPhase ? { phase: normalizedPhase } : {}),
53
+ ...(active ? { active: true } : {}),
54
+ offset: start,
55
+ ...(Number.isInteger(limit) && limit >= 0 ? { limit } : {}),
56
+ total: filtered.length,
57
+ hasMore: end !== undefined ? end < filtered.length : false,
38
58
  };
39
59
  }
40
60