@planu/cli 5.7.4 → 5.7.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 (208) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +3 -3
  3. package/dist/.planu-build.json +1 -1
  4. package/dist/cli/commands/package-handoff.js +30 -5
  5. package/dist/config/compliance-profiles.json +36 -3
  6. package/dist/config/environment-schema.json +21 -0
  7. package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
  8. package/dist/config/official-sdd-tools.d.ts +1 -1
  9. package/dist/config/official-sdd-tools.js +11 -24
  10. package/dist/config/registries/hosts/codex.json +14 -30
  11. package/dist/config/registries/hosts/opencode.json +1 -6
  12. package/dist/config/registries/legacy-guidance.json +26 -0
  13. package/dist/config/server-instructions.js +0 -21
  14. package/dist/config/skill-templates/planu-context-assets.md +3 -6
  15. package/dist/config/skill-templates/planu-implement.md +2 -2
  16. package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
  17. package/dist/config/skill-templates/planu-release.md +2 -7
  18. package/dist/config/skill-templates/planu-validate.md +3 -3
  19. package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
  20. package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
  21. package/dist/config/spec-templates/stripe-payments/template.json +5 -1
  22. package/dist/config/spec-templates/webhook-system/template.json +5 -1
  23. package/dist/config/subagent-templates/planu-challenger.md +4 -9
  24. package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
  25. package/dist/config/subagent-templates/planu-validator.md +5 -8
  26. package/dist/config/tool-groups.json +6 -144
  27. package/dist/engine/ambiguity-scorer.js +40 -1
  28. package/dist/engine/autopilot/bootstrap.js +1 -1
  29. package/dist/engine/code-impact-analyzer.js +2 -6
  30. package/dist/engine/context-orchestrator/index.js +2 -2
  31. package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
  32. package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
  33. package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
  34. package/dist/engine/evidence-gates/artifact-reader.js +1 -1
  35. package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
  36. package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
  37. package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
  38. package/dist/engine/evidence-index/index-builder.js +21 -4
  39. package/dist/engine/execution/job-runtime.js +1 -1
  40. package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
  41. package/dist/engine/execution/package-handoff-job-executor.js +490 -0
  42. package/dist/engine/execution/validate-job-executor.js +20 -8
  43. package/dist/engine/git/exec-git.d.ts +4 -0
  44. package/dist/engine/git/exec-git.js +14 -0
  45. package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
  46. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
  47. package/dist/engine/handoff-artifacts/schemas.js +1 -0
  48. package/dist/engine/handoff-format.d.ts +36 -0
  49. package/dist/engine/handoff-format.js +314 -0
  50. package/dist/engine/handoff-packager.d.ts +5 -3
  51. package/dist/engine/handoff-packager.js +89 -32
  52. package/dist/engine/host-tool-filter.js +20 -49
  53. package/dist/engine/human-summary.js +14 -3
  54. package/dist/engine/implementation-contract/common.d.ts +8 -3
  55. package/dist/engine/implementation-contract/common.js +9 -6
  56. package/dist/engine/implementation-contract/evaluator.js +4 -6
  57. package/dist/engine/implementation-contract/renderer.js +18 -23
  58. package/dist/engine/project-health-checker.js +2 -2
  59. package/dist/engine/readiness-checker.js +27 -0
  60. package/dist/engine/reconcile/apply-changes.js +4 -0
  61. package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
  62. package/dist/engine/reconcile/propagate-mirrors.js +311 -0
  63. package/dist/engine/self-healing/healer.js +12 -5
  64. package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
  65. package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
  66. package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
  67. package/dist/engine/self-healing/strategies/test-fix.js +32 -14
  68. package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
  69. package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
  70. package/dist/engine/session/checkpoint-writer.js +8 -4
  71. package/dist/engine/session/session-tracker.d.ts +5 -16
  72. package/dist/engine/session/session-tracker.js +29 -26
  73. package/dist/engine/session-state/writer.js +3 -11
  74. package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
  75. package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
  76. package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
  77. package/dist/engine/spec-format/bdd-parser.js +40 -11
  78. package/dist/engine/spec-format/lean-spec-generator.js +30 -10
  79. package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
  80. package/dist/engine/spec-format/retired-scaffold.js +5 -0
  81. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
  82. package/dist/engine/spec-format/technical-md-populator.js +27 -0
  83. package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
  84. package/dist/engine/spec-format/unified-spec-builder.js +30 -20
  85. package/dist/engine/spec-grounding/contract.js +115 -1
  86. package/dist/engine/spec-migrator/fold-technical.js +60 -19
  87. package/dist/engine/spec-migrator/placeholder-technical.d.ts +2 -0
  88. package/dist/engine/spec-migrator/placeholder-technical.js +18 -0
  89. package/dist/engine/spec-quality/generic-output-gate.js +37 -3
  90. package/dist/engine/technical-enricher/index.js +4 -1
  91. package/dist/engine/type-safety-gate.js +3 -11
  92. package/dist/engine/universal-rules/catalog.js +0 -2
  93. package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
  94. package/dist/engine/validation/durable-validation.d.ts +3 -2
  95. package/dist/engine/validation/durable-validation.js +106 -40
  96. package/dist/engine/validation/validation-freshness.d.ts +3 -2
  97. package/dist/engine/validation/validation-freshness.js +22 -16
  98. package/dist/engine/validation/validation-worktree.js +34 -0
  99. package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
  100. package/dist/index.js +3 -17
  101. package/dist/resources/process.js +38 -65
  102. package/dist/storage/session-state-store.js +2 -0
  103. package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
  104. package/dist/tools/configure-checkpoint-policy.js +3 -3
  105. package/dist/tools/create-spec-helpers.js +2 -5
  106. package/dist/tools/create-spec.js +21 -4
  107. package/dist/tools/facilitate.js +3 -6
  108. package/dist/tools/git/branch-ops.js +16 -4
  109. package/dist/tools/github-release-handler.js +1 -20
  110. package/dist/tools/heal-spec-docs.js +1 -13
  111. package/dist/tools/init-project/agents-md-writer.js +6 -6
  112. package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
  113. package/dist/tools/init-project/claude-md-generator.js +0 -35
  114. package/dist/tools/init-project/handler.js +24 -49
  115. package/dist/tools/init-project/legacy-guidance-scanner.d.ts +12 -0
  116. package/dist/tools/init-project/legacy-guidance-scanner.js +176 -0
  117. package/dist/tools/init-project/per-client-files-writer.js +5 -5
  118. package/dist/tools/init-project/planu-workflow-generator.js +2 -13
  119. package/dist/tools/init-project/portable-index-reconciler.d.ts +21 -1
  120. package/dist/tools/init-project/portable-index-reconciler.js +66 -0
  121. package/dist/tools/init-project/rules-generator.d.ts +0 -11
  122. package/dist/tools/init-project/rules-generator.js +4 -85
  123. package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
  124. package/dist/tools/init-project/scaffold-writer.js +1 -12
  125. package/dist/tools/jobs/handlers.d.ts +0 -6
  126. package/dist/tools/jobs/handlers.js +13 -40
  127. package/dist/tools/list-specs.js +6 -21
  128. package/dist/tools/package-handoff.d.ts +3 -2
  129. package/dist/tools/package-handoff.js +153 -427
  130. package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
  131. package/dist/tools/register-sdd-tools.js +0 -23
  132. package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
  133. package/dist/tools/schemas/index.d.ts +1 -1
  134. package/dist/tools/schemas/index.js +1 -1
  135. package/dist/tools/schemas/output-schemas.d.ts +1 -18
  136. package/dist/tools/schemas/output-schemas.js +1 -11
  137. package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
  138. package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
  139. package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
  140. package/dist/tools/schemas/validate-output-schema.js +3 -2
  141. package/dist/tools/session-checkpoint.js +2 -2
  142. package/dist/tools/skill-registry/index.d.ts +0 -1
  143. package/dist/tools/skill-registry/index.js +0 -1
  144. package/dist/tools/sync-spec-state-handler.d.ts +2 -2
  145. package/dist/tools/sync-spec-state-handler.js +23 -3
  146. package/dist/tools/tool-registry/core-tools.js +1 -140
  147. package/dist/tools/tool-registry/group-infra.js +2 -39
  148. package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
  149. package/dist/tools/update-status/batch.js +17 -0
  150. package/dist/tools/update-status/dod-gates.js +14 -1
  151. package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
  152. package/dist/tools/update-status/done-receipt-verifier.js +56 -26
  153. package/dist/tools/update-status/evidence-gate.js +10 -21
  154. package/dist/tools/update-status/file-sync.d.ts +1 -0
  155. package/dist/tools/update-status/file-sync.js +6 -0
  156. package/dist/tools/update-status/index.d.ts +6 -0
  157. package/dist/tools/update-status/index.js +39 -44
  158. package/dist/tools/update-status-actions.js +2 -7
  159. package/dist/tools/validate.js +34 -28
  160. package/dist/transports/oauth-validator.js +12 -1
  161. package/dist/transports/transport-factory.d.ts +2 -1
  162. package/dist/transports/transport-factory.js +19 -0
  163. package/dist/types/common/primitives.d.ts +2 -0
  164. package/dist/types/durable-job.d.ts +45 -0
  165. package/dist/types/durable-validation.d.ts +1 -1
  166. package/dist/types/evidence-autofill.d.ts +13 -1
  167. package/dist/types/handoff-artifacts.d.ts +2 -1
  168. package/dist/types/index.d.ts +1 -0
  169. package/dist/types/index.js +1 -0
  170. package/dist/types/legacy-guidance.d.ts +20 -0
  171. package/dist/types/legacy-guidance.js +3 -0
  172. package/dist/types/readiness.d.ts +8 -1
  173. package/dist/types/reconcile.d.ts +6 -0
  174. package/dist/types/skill-registry.d.ts +26 -1
  175. package/dist/types/spec/core.d.ts +5 -0
  176. package/dist/types/spec/inputs.d.ts +2 -2
  177. package/dist/types/spec-format.d.ts +11 -1
  178. package/dist/types/spec-grounding.d.ts +12 -0
  179. package/dist/types/ssr-migration.d.ts +1 -1
  180. package/dist/types/transport.d.ts +1 -0
  181. package/dist/types/validation-receipt.d.ts +27 -0
  182. package/package.json +3 -2
  183. package/planu-plugin.json +13 -26
  184. package/src/i18n/messages/en.json +1 -1
  185. package/src/i18n/messages/es.json +1 -1
  186. package/src/i18n/messages/pt.json +1 -1
  187. package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
  188. package/dist/engine/skill-generator/conventions-hasher.js +0 -24
  189. package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
  190. package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
  191. package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
  192. package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
  193. package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
  194. package/dist/tools/clarify-requirements/questions-context.js +0 -74
  195. package/dist/tools/clarify-requirements/questions.d.ts +0 -16
  196. package/dist/tools/clarify-requirements/questions.js +0 -96
  197. package/dist/tools/clarify-requirements.d.ts +0 -4
  198. package/dist/tools/clarify-requirements.js +0 -314
  199. package/dist/tools/code-graph-handler.d.ts +0 -6
  200. package/dist/tools/code-graph-handler.js +0 -72
  201. package/dist/tools/create-rule.d.ts +0 -4
  202. package/dist/tools/create-rule.js +0 -107
  203. package/dist/tools/feedback-handler.d.ts +0 -7
  204. package/dist/tools/feedback-handler.js +0 -150
  205. package/dist/tools/semantic-search-handler.d.ts +0 -7
  206. package/dist/tools/semantic-search-handler.js +0 -71
  207. package/dist/tools/skill-registry/search.d.ts +0 -11
  208. package/dist/tools/skill-registry/search.js +0 -159
@@ -1,457 +1,183 @@
1
- // tools/package-handoff.ts — Handoff context packager tool (SPEC-039)
2
- import { specStore, knowledgeStore } from '../storage/index.js';
3
- import { checkSpecReadiness } from '../engine/readiness-checker.js';
4
- import { packageHandoff } from '../engine/handoff-packager.js';
5
- import { detectParadigms } from '../engine/paradigm-detector.js';
6
- import { analyzeContextPreflight, buildTokenWasteReport, loadTokenWastePolicy, recommendRelevantTools, toolsFromPolicyGroups, } from '../engine/token-optimizer/index.js';
7
- import { appendTransitionEvent } from '../storage/transition-log.js';
8
- import { formatProjectGraphContext, queryProjectGraphSlice, } from '../engine/project-graph/index.js';
9
- import { analyzeMinimalImplementation, loadMinimalImplementationPolicy, } from '../engine/minimality/index.js';
10
- // ── Formatting helpers ───────────────────────────────────────────────────────
11
- export function formatHandoff(pkg) {
12
- const lines = [];
13
- lines.push(`# Implementation Handoff Package — ${pkg.specId}`);
14
- lines.push('');
15
- lines.push(`Generated: ${pkg.generatedAt}`);
16
- lines.push(`Readiness Score: ${pkg.readinessScore}/100`);
17
- if (pkg.handoffPath) {
18
- lines.push(`Handoff Path: ${pkg.handoffPath}`);
19
- }
20
- if (pkg.contextHash) {
21
- lines.push(`Context Hash: ${pkg.contextHash}`);
22
- }
23
- lines.push(`Blocked: ${pkg.blocked ? 'yes' : 'no'}`);
24
- lines.push('');
25
- lines.push('## Objective');
26
- lines.push('');
27
- lines.push(pkg.objective);
28
- lines.push('');
29
- lines.push('## Acceptance Criteria');
30
- lines.push('');
31
- if (pkg.criteria.length > 0) {
32
- for (const criterion of pkg.criteria) {
33
- lines.push(`- [ ] ${criterion}`);
1
+ // tools/package-handoff.ts — Handoff context packager tool (SPEC-039, durable submission SPEC-1240)
2
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
3
+ if (value !== null && value !== void 0) {
4
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
5
+ var dispose, inner;
6
+ if (async) {
7
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
8
+ dispose = value[Symbol.asyncDispose];
34
9
  }
35
- }
36
- else {
37
- lines.push('_(No criteria found — check spec.md acceptance criteria)_');
38
- }
39
- lines.push('');
40
- lines.push('## Files to Modify');
41
- lines.push('');
42
- if (pkg.filesToModify.length > 0) {
43
- for (const f of pkg.filesToModify) {
44
- lines.push(`- ${f}`);
10
+ if (dispose === void 0) {
11
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
12
+ dispose = value[Symbol.dispose];
13
+ if (async) inner = dispose;
45
14
  }
46
- }
47
- else {
48
- lines.push('_(No files identified check spec.md ## Technical / ## Files)_');
49
- }
50
- lines.push('');
51
- lines.push('## Files to Create');
52
- lines.push('');
53
- if (pkg.filesToCreate.length > 0) {
54
- for (const f of pkg.filesToCreate) {
55
- lines.push(`- ${f}`);
15
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
16
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
17
+ env.stack.push({ value: value, dispose: dispose, async: async });
18
+ }
19
+ else if (async) {
20
+ env.stack.push({ async: true });
21
+ }
22
+ return value;
23
+ };
24
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
25
+ return function (env) {
26
+ function fail(e) {
27
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
28
+ env.hasError = true;
56
29
  }
57
- }
58
- else {
59
- lines.push('_(None identified)_');
60
- }
61
- lines.push('');
62
- lines.push('## Test Files');
63
- lines.push('');
64
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
65
- const filesToTest = pkg.filesToTest ?? [];
66
- if (filesToTest.length > 0) {
67
- for (const f of filesToTest) {
68
- lines.push(`- ${f}`);
69
- }
70
- }
71
- else {
72
- lines.push('_(None identified)_');
73
- }
74
- lines.push('');
75
- lines.push('## Ownership');
76
- lines.push('');
77
- if (pkg.ownership.length > 0) {
78
- for (const item of pkg.ownership) {
79
- lines.push(`- ${item}`);
80
- }
81
- }
82
- else {
83
- lines.push('_(Missing ownership — handoff is blocked)_');
84
- }
85
- lines.push('');
86
- lines.push('## Test Plan');
87
- lines.push('');
88
- if (pkg.testPlan.length > 0) {
89
- for (const item of pkg.testPlan) {
90
- lines.push(`- ${item}`);
91
- }
92
- }
93
- else {
94
- lines.push('_(Missing test plan — handoff is blocked)_');
95
- }
96
- lines.push('');
97
- lines.push('## Risks');
98
- lines.push('');
99
- if (pkg.risks.length > 0) {
100
- for (const item of pkg.risks) {
101
- lines.push(`- ${item}`);
102
- }
103
- }
104
- else {
105
- lines.push('_(Missing risk analysis — handoff is blocked)_');
106
- }
107
- lines.push('');
108
- lines.push('## OUT OF SCOPE');
109
- lines.push('');
110
- lines.push('**The following must NOT be implemented:**');
111
- lines.push('');
112
- for (const item of pkg.outOfScope) {
113
- lines.push(`- ${item}`);
114
- }
115
- lines.push('');
116
- if (pkg.constraints.length > 0) {
117
- lines.push('## Hard Constraints');
118
- lines.push('');
119
- for (const constraint of pkg.constraints) {
120
- lines.push(`- [${constraint.type.toUpperCase()}] ${constraint.description}`);
121
- if (constraint.affectedItems.length > 0) {
122
- lines.push(` Affects: ${constraint.affectedItems.join(', ')}`);
30
+ var r, s = 0;
31
+ function next() {
32
+ while (r = env.stack.pop()) {
33
+ try {
34
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
35
+ if (r.dispose) {
36
+ var result = r.dispose.call(r.value);
37
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
38
+ }
39
+ else s |= 1;
40
+ }
41
+ catch (e) {
42
+ fail(e);
43
+ }
123
44
  }
45
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
46
+ if (env.hasError) throw env.error;
124
47
  }
125
- lines.push('');
126
- }
127
- if (pkg.warnings.length > 0) {
128
- lines.push('## Warnings');
129
- lines.push('');
130
- for (const warning of pkg.warnings) {
131
- lines.push(`- WARNING: ${warning}`);
132
- }
133
- lines.push('');
134
- }
135
- lines.push('## Current State');
136
- lines.push('');
137
- lines.push(pkg.currentState);
138
- lines.push('');
139
- lines.push('## Next Action');
140
- lines.push('');
141
- lines.push(pkg.nextAction);
142
- lines.push('');
143
- if (pkg.blockers.length > 0) {
144
- lines.push('## Blockers');
145
- lines.push('');
146
- for (const blocker of pkg.blockers) {
147
- lines.push(`- ${blocker}`);
148
- }
149
- lines.push('');
150
- }
151
- return lines.join('\n');
152
- }
153
- function topItems(items, limit) {
154
- return items.slice(0, limit);
155
- }
156
- function buildHandoffSummaryText(args) {
157
- const lines = [];
158
- lines.push(`# Implementation Handoff Package - ${args.pkg.specId}`);
159
- lines.push('');
160
- lines.push(`Readiness Score: ${String(args.pkg.readinessScore)}/100`);
161
- lines.push(`Blocked: ${args.pkg.blocked ? 'yes' : 'no'}`);
162
- if (args.pkg.handoffPath) {
163
- lines.push(`Handoff Path: ${args.pkg.handoffPath}`);
164
- }
165
- if (args.pkg.contextHash) {
166
- lines.push(`Context Hash: ${args.pkg.contextHash}`);
167
- }
168
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
169
- const filesToTestCount = (args.pkg.filesToTest ?? []).length;
170
- lines.push(`Counts: ${String(args.pkg.criteria.length)} criteria, ${String(args.pkg.filesToModify.length)} files to modify, ${String(args.pkg.filesToCreate.length)} files to create, ${String(filesToTestCount)} files to test, ${String(args.pkg.testPlan.length)} tests, ${String(args.pkg.risks.length)} risks.`);
171
- if (args.topBlockers.length > 0) {
172
- lines.push(`Top blockers: ${args.topBlockers.join('; ')}`);
173
- }
174
- if (args.topRisks.length > 0) {
175
- lines.push(`Top risks: ${args.topRisks.join('; ')}`);
176
- }
177
- if (args.minimalityBlocked !== null) {
178
- lines.push(`Minimality: ${args.minimalityBlocked ? 'blocked' : 'pass'}`);
179
- }
180
- if (args.tokenWasteRiskCount !== null) {
181
- lines.push(`Token Waste Autopilot risks: ${String(args.tokenWasteRiskCount)}`);
182
- }
183
- lines.push(`Next Action: ${args.pkg.nextAction}`);
184
- lines.push('');
185
- lines.push('Full handoff is available via the handoffPath artifact; it is not embedded by default.');
186
- return lines.join('\n');
187
- }
188
- function summarizeMinimality(report) {
189
- if (report === null) {
190
- return undefined;
191
- }
192
- const blockingFindings = report.findings.filter((finding) => finding.blocksDone);
193
- return {
194
- enabled: report.enabled,
195
- blocked: report.blocked,
196
- findingCount: report.findings.length,
197
- blockingFindingCount: blockingFindings.length,
198
- blockersTop3: topItems(blockingFindings, 3).map((finding) => ({
199
- ruleId: finding.ruleId,
200
- target: finding.target,
201
- evidence: finding.evidence,
202
- })),
203
- safetyExceptionCount: report.safetyExceptions.length,
204
- debtEvidenceCount: report.debtEvidence.length,
48
+ return next();
205
49
  };
50
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
51
+ var e = new Error(message);
52
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
53
+ });
54
+ import { createHash } from 'node:crypto';
55
+ import { specStore } from '../storage/index.js';
56
+ import { DurableJobRuntime, DurableJobNotFoundError, publicJobCursor, } from '../engine/execution/job-runtime.js';
57
+ import { schedulePackageHandoffJobExecution } from '../engine/execution/package-handoff-job-executor.js';
58
+ import { acquireLock, releaseLock } from '../engine/safety/cross-process-lock.js';
59
+ import { resolveProjectId, missingProjectIdError } from './resolve-project-id.js';
60
+ export { formatHandoff } from '../engine/handoff-format.js';
61
+ const SUBMISSION_LOCK_WAIT_MS = 5_000;
62
+ const SUBMISSION_LOCK_POLL_MS = 100;
63
+ const TERMINAL_RECOVERABLE_STATES = new Set(['failed', 'cancelled', 'dead-letter']);
64
+ function deriveOperationId(projectId, specId, specVersion) {
65
+ const digest = createHash('sha256').update(`${projectId}|${specId}|${specVersion}`).digest('hex');
66
+ return `handoff-${digest}`;
206
67
  }
207
- function summarizeTokenWaste(report) {
208
- if (report === null) {
209
- return undefined;
210
- }
211
- return {
212
- actionsTakenCount: report.actionsTaken.length,
213
- recommendationCount: report.recommendations.length,
214
- riskCount: report.risks.length,
215
- risksTop3: topItems(report.risks, 3).map((risk) => ({
216
- decision: risk.decision,
217
- target: risk.target,
218
- reason: risk.reason,
219
- confidence: risk.confidence,
220
- })),
221
- modelEffort: {
222
- effort: report.modelEffort.effort,
223
- reason: report.modelEffort.reason,
224
- },
225
- };
226
- }
227
- function summarizeGraphContext(graphContext) {
228
- if (graphContext === null || typeof graphContext !== 'object') {
229
- return { available: false };
230
- }
231
- const graph = graphContext;
232
- return {
233
- available: true,
234
- nodeCount: Array.isArray(graph.nodes) ? graph.nodes.length : 0,
235
- edgeCount: Array.isArray(graph.edges) ? graph.edges.length : 0,
236
- summary: graph.summary,
237
- tokenSavings: graph.tokenSavings,
238
- freshness: graph.freshness
239
- ? {
240
- graphPath: graph.freshness.graphPath,
241
- stale: graph.freshness.stale,
242
- reason: graph.freshness.reason,
243
- }
244
- : undefined,
245
- };
246
- }
247
- async function buildHandoffTokenWasteReport(pkg, knowledge, spec) {
248
- try {
249
- const { policy } = await loadTokenWastePolicy(knowledge.projectPath);
250
- if (!policy.enabled) {
251
- return null;
252
- }
253
- const context = analyzeContextPreflight({
254
- action: 'package_handoff',
255
- policy,
256
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
257
- candidates: [...pkg.filesToModify, ...pkg.filesToCreate, ...(pkg.filesToTest ?? [])].map((path) => ({
258
- path,
259
- reason: 'Referenced by handoff package.',
260
- })),
261
- spec: {
262
- id: spec.id,
263
- risk: spec.risk,
264
- target: spec.target,
265
- },
68
+ async function acquireSubmissionLock(projectId, operationId) {
69
+ const deadlineAt = Date.now() + SUBMISSION_LOCK_WAIT_MS;
70
+ while (Date.now() < deadlineAt) {
71
+ const handle = await acquireLock(projectId, `${operationId}-submit`, {
72
+ reason: 'durable-handoff-submit',
266
73
  });
267
- const tools = recommendRelevantTools({
268
- action: 'package_handoff',
269
- intentSignals: ['handoff', spec.scope ?? '', spec.target ?? ''].filter(Boolean),
270
- tools: toolsFromPolicyGroups(policy),
271
- policy,
272
- });
273
- return buildTokenWasteReport({
274
- action: 'package_handoff',
275
- policy,
276
- context,
277
- tools,
278
- spec: {
279
- id: spec.id,
280
- risk: spec.risk,
281
- scope: spec.scope,
282
- },
283
- });
284
- }
285
- catch {
286
- return null;
287
- }
288
- }
289
- async function buildGraphContextSection(args) {
290
- if (args.projectPath === undefined) {
291
- return null;
292
- }
293
- try {
294
- const slice = await queryProjectGraphSlice({
295
- projectId: args.projectId,
296
- projectPath: args.projectPath,
297
- specId: args.specId,
298
- });
299
- if (slice === null || slice.nodes.length === 0) {
300
- return null;
74
+ if (handle) {
75
+ return handle;
76
+ }
77
+ const remainingMs = deadlineAt - Date.now();
78
+ if (remainingMs > 0) {
79
+ await new Promise((resolve) => setTimeout(resolve, Math.min(SUBMISSION_LOCK_POLL_MS, remainingMs)));
301
80
  }
302
- return { text: await formatProjectGraphContext(slice), structured: slice };
303
- }
304
- catch {
305
- return null;
306
- }
307
- }
308
- async function buildMinimalImplementationReport(pkg, knowledge, spec, handoffText) {
309
- try {
310
- const { policy, evidence } = await loadMinimalImplementationPolicy(knowledge.projectPath);
311
- return analyzeMinimalImplementation({
312
- policy,
313
- specId: spec.id,
314
- specRisk: spec.risk,
315
- handoffText,
316
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
317
- files: [...pkg.filesToModify, ...pkg.filesToCreate, ...(pkg.filesToTest ?? [])].map((path) => ({ path, content: path })),
318
- evidence,
319
- });
320
- }
321
- catch {
322
- return null;
323
81
  }
82
+ throw new Error('[Planu] Timed out waiting for the package_handoff submission lock');
324
83
  }
325
- // ── Handler ──────────────────────────────────────────────────────────────────
84
+ /** Public MCP boundary: submit the handoff job and acknowledge without packaging inline. */
326
85
  export async function handlePackageHandoff(args) {
327
- const { projectId, specId } = args;
328
- const spec = await specStore.getSpec(projectId, specId);
329
- if (!spec) {
330
- return {
331
- content: [
332
- {
333
- type: 'text',
334
- text: `Spec '${specId}' not found in project '${projectId}'`,
335
- },
336
- ],
337
- isError: true,
338
- };
86
+ const projectId = resolveProjectId(args);
87
+ if (!projectId) {
88
+ return missingProjectIdError;
339
89
  }
340
- const knowledge = await knowledgeStore.getKnowledge(projectId);
341
- if (!knowledge) {
90
+ const { specId } = args;
91
+ const spec = await specStore.getSpec(projectId, specId, args.projectPath);
92
+ if (!spec) {
342
93
  return {
343
94
  content: [
344
- {
345
- type: 'text',
346
- text: `Project '${projectId}' not found. Run init_project first.`,
347
- },
95
+ { type: 'text', text: `Spec '${specId}' not found in project '${projectId}'` },
348
96
  ],
349
97
  isError: true,
98
+ structuredContent: { error: 'spec_not_found', code: 404, context: { specId, projectId } },
350
99
  };
351
100
  }
352
- // Run readiness check to populate score
353
- const readinessReport = await checkSpecReadiness(spec, 'lenient');
354
- const pkg = await packageHandoff(spec, knowledge);
355
- // SPEC-035b: Inject paradigm constraint when project path is available
356
- const paradigmConstraints = pkg.constraints.slice();
357
- if (knowledge.projectPath) {
101
+ const specVersion = spec.spec_version ?? '0.0.0';
102
+ const operationId = deriveOperationId(projectId, specId, specVersion);
103
+ const submissionLock = await acquireSubmissionLock(projectId, operationId);
104
+ try {
105
+ const env_1 = { stack: [], error: void 0, hasError: false };
358
106
  try {
359
- const paradigmReport = await detectParadigms(knowledge.projectPath);
360
- const dominant = paradigmReport.dominant;
361
- if (dominant && dominant.confidence >= 0.5) {
362
- paradigmConstraints.push({
363
- type: 'interface',
364
- description: `Follow ${dominant.name} paradigm patterns — dominant paradigm (confidence: ${(dominant.confidence * 100).toFixed(0)}%)`,
365
- affectedItems: ['all new code'],
107
+ const runtime = __addDisposableResource(env_1, new DurableJobRuntime(), false);
108
+ let existing;
109
+ try {
110
+ existing = runtime.get({ operationId, projectId });
111
+ }
112
+ catch (error) {
113
+ if (!(error instanceof DurableJobNotFoundError)) {
114
+ throw error;
115
+ }
116
+ }
117
+ if (existing && !TERMINAL_RECOVERABLE_STATES.has(existing.state)) {
118
+ if (existing.state === 'accepted' || existing.state === 'checkpointed') {
119
+ schedulePackageHandoffJobExecution(projectId, existing.workspaceId);
120
+ }
121
+ return acceptedResult(existing, projectId, runtime, { justCreated: false });
122
+ }
123
+ if (existing && TERMINAL_RECOVERABLE_STATES.has(existing.state)) {
124
+ return acceptedResult(existing, projectId, runtime, {
125
+ justCreated: false,
126
+ recoveryAction: 'restart_job',
366
127
  });
367
128
  }
129
+ const job = runtime.accept({
130
+ projectId,
131
+ operationId,
132
+ payload: {
133
+ kind: 'package-handoff',
134
+ schemaVersion: 1,
135
+ projectId,
136
+ specId,
137
+ specVersion,
138
+ },
139
+ });
140
+ schedulePackageHandoffJobExecution(projectId, job.workspaceId);
141
+ return acceptedResult(job, projectId, runtime, { justCreated: true });
142
+ }
143
+ catch (e_1) {
144
+ env_1.error = e_1;
145
+ env_1.hasError = true;
368
146
  }
369
- catch {
370
- // Paradigm detection is best-effort — never block handoff
147
+ finally {
148
+ __disposeResources(env_1);
371
149
  }
372
150
  }
373
- // Inject readiness score
374
- const pkgWithScore = {
375
- ...pkg,
376
- readinessScore: readinessReport.score,
377
- constraints: paradigmConstraints,
378
- };
379
- const tokenWasteReport = await buildHandoffTokenWasteReport(pkgWithScore, knowledge, spec);
380
- const graphContext = await buildGraphContextSection({
381
- projectId,
382
- projectPath: knowledge.projectPath,
383
- specId,
384
- });
385
- const baseHandoffText = graphContext !== null
386
- ? `${formatHandoff(pkgWithScore)}\n${graphContext.text}`
387
- : formatHandoff(pkgWithScore);
388
- const minimalityReport = await buildMinimalImplementationReport(pkgWithScore, knowledge, spec, baseHandoffText);
389
- void appendTransitionEvent({
151
+ finally {
152
+ await releaseLock(submissionLock);
153
+ }
154
+ }
155
+ function acceptedResult(job, projectId, runtime, { justCreated, recoveryAction }) {
156
+ const observation = runtime.observe(job);
157
+ const accepted = {
158
+ schemaVersion: 1,
159
+ operationId: job.id,
390
160
  projectId,
391
- specId,
392
- eventType: 'handoff.intake',
393
- gateResults: { handoff: pkgWithScore.blocked ? 'fail' : 'pass' },
394
- meta: {
395
- handoffPath: pkgWithScore.handoffPath,
396
- contextHash: pkgWithScore.contextHash,
397
- blockers: pkgWithScore.blockers,
398
- },
399
- }).catch(() => {
400
- /* best-effort handoff rendering should not fail because telemetry failed */
401
- });
402
- const topBlockers = topItems(pkgWithScore.blockers, 3);
403
- const ownershipBlockers = pkgWithScore.blockers.filter((blocker) => blocker.startsWith('handoff_ownership_invalid'));
404
- const topRisks = topItems(pkgWithScore.risks, 5);
405
- const minimalitySummary = summarizeMinimality(minimalityReport);
406
- const tokenWasteSummary = summarizeTokenWaste(tokenWasteReport);
407
- const graphSummary = graphContext !== null ? summarizeGraphContext(graphContext.structured) : undefined;
161
+ workspaceId: job.workspaceId,
162
+ state: job.state,
163
+ submittedAt: justCreated ? job.updatedAt : job.createdAt,
164
+ attachedAt: justCreated ? job.updatedAt : new Date().toISOString(),
165
+ reused: !justCreated,
166
+ cursor: publicJobCursor(observation),
167
+ queue: observation.queue,
168
+ terminalStates: ['completed', 'failed', 'cancelled', 'dead-letter'],
169
+ resultSchema: 'package-handoff-completion/v1',
170
+ observation: { get: 'get_job', restart: 'restart_job' },
171
+ ...(recoveryAction ? { recoveryAction } : {}),
172
+ };
173
+ const text = recoveryAction
174
+ ? `Handoff job ${job.id} ended as ${job.state}. Call restart_job to retry, or resubmit package_handoff once fixed.`
175
+ : justCreated
176
+ ? `Handoff accepted as durable job ${job.id}.`
177
+ : `Attached to already-running handoff job ${job.id}; no new work was scheduled. Follow it with get_job.`;
408
178
  return {
409
- content: [
410
- {
411
- type: 'text',
412
- text: buildHandoffSummaryText({
413
- pkg: pkgWithScore,
414
- topBlockers,
415
- topRisks,
416
- minimalityBlocked: minimalityReport?.blocked ?? null,
417
- tokenWasteRiskCount: tokenWasteReport?.risks.length ?? null,
418
- }),
419
- },
420
- ],
421
- structuredContent: {
422
- specId,
423
- blocked: pkgWithScore.blocked,
424
- readinessScore: pkgWithScore.readinessScore,
425
- blockersCount: pkgWithScore.blockers.length,
426
- blockersTop3: topBlockers,
427
- ownershipBlockers,
428
- risksTop5: topRisks,
429
- criteriaTop5: topItems(pkgWithScore.criteria, 5),
430
- constraintsTop5: topItems(pkgWithScore.constraints, 5),
431
- warningsTop5: topItems(pkgWithScore.warnings, 5),
432
- handoffPath: pkgWithScore.handoffPath,
433
- contextHash: pkgWithScore.contextHash,
434
- artifactRefs: {
435
- handoff: pkgWithScore.handoffPath,
436
- },
437
- counts: {
438
- criteria: pkgWithScore.criteria.length,
439
- filesToModify: pkgWithScore.filesToModify.length,
440
- filesToCreate: pkgWithScore.filesToCreate.length,
441
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
442
- filesToTest: (pkgWithScore.filesToTest ?? []).length,
443
- ownership: pkgWithScore.ownership.length,
444
- testPlan: pkgWithScore.testPlan.length,
445
- risks: pkgWithScore.risks.length,
446
- constraints: pkgWithScore.constraints.length,
447
- warnings: pkgWithScore.warnings.length,
448
- outOfScope: pkgWithScore.outOfScope.length,
449
- },
450
- nextAction: pkgWithScore.nextAction,
451
- ...(minimalitySummary !== undefined ? { minimality: minimalitySummary } : {}),
452
- ...(tokenWasteSummary !== undefined ? { tokenWaste: tokenWasteSummary } : {}),
453
- ...(graphSummary !== undefined ? { graphContext: graphSummary } : {}),
454
- },
179
+ content: [{ type: 'text', text }],
180
+ structuredContent: accepted,
455
181
  };
456
182
  }
457
183
  //# sourceMappingURL=package-handoff.js.map
@@ -60,21 +60,28 @@ async function installHookFile(projectPath) {
60
60
  '{',
61
61
  ' echo "[$(date -u +%FT%TZ)] session-safeguard fired"',
62
62
  ' cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0',
63
- ' current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")',
64
- ' case "$current" in',
65
- ' ""|HEAD|main|master|develop|release|production)',
66
- ' # SPEC-1379: never autopush protected branches mirrors PROTECTED_BRANCHES',
67
- ' # in src/engine/session-safeguard/autopush.ts. --no-verify below is safe',
68
- ' # ONLY because of this guard; remove one, remove both.',
69
- ' echo "skip: protected or detached ($current)"',
70
- ' ;;',
71
- ' *)',
72
- ' # No --force: the remote rejects any non-fast-forward push, so this can',
73
- ' # never overwrite remote history. --end-of-options hardens against',
74
- ' # refnames that start with a dash.',
75
- ' git push --no-verify --end-of-options origin "$current" 2>&1 || echo "push failed or nothing to push"',
76
- ' ;;',
77
- ' esac',
63
+ ' if [ -n "${PLANU_NO_AUTOPUSH:-}" ] || [ -f "${CLAUDE_PROJECT_DIR:-.}/.claude/no-autopush" ]; then',
64
+ ' echo "skip: autopush opt-out active"',
65
+ ' else',
66
+ ' current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")',
67
+ ' case "$current" in',
68
+ ' ""|HEAD|main|master|develop|release|production)',
69
+ ' # SPEC-1379: never autopush protected branches mirrors PROTECTED_BRANCHES',
70
+ ' # in src/engine/session-safeguard/autopush.ts.',
71
+ ' echo "skip: protected or detached ($current)"',
72
+ ' ;;',
73
+ ' *)',
74
+ ' if git rev-parse --verify --quiet "refs/remotes/origin/$current" >/dev/null; then',
75
+ ' # No --force: the remote rejects any non-fast-forward push, so this can',
76
+ ' # never overwrite remote history. --end-of-options hardens against',
77
+ ' # refnames that start with a dash.',
78
+ ' git push --end-of-options origin "$current" 2>&1 || echo "push failed or nothing to push"',
79
+ ' else',
80
+ ' echo "skip: no upstream for $current (no-upstream)"',
81
+ ' fi',
82
+ ' ;;',
83
+ ' esac',
84
+ ' fi',
78
85
  '} >> "$log" 2>&1',
79
86
  'exit 0',
80
87
  ].join('\n') + '\n';