@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.
- package/CHANGELOG.md +85 -0
- package/README.md +3 -3
- package/dist/.planu-build.json +1 -1
- package/dist/cli/commands/package-handoff.js +30 -5
- package/dist/config/compliance-profiles.json +36 -3
- package/dist/config/environment-schema.json +21 -0
- package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
- package/dist/config/official-sdd-tools.d.ts +1 -1
- package/dist/config/official-sdd-tools.js +11 -24
- package/dist/config/registries/hosts/codex.json +14 -30
- package/dist/config/registries/hosts/opencode.json +1 -6
- package/dist/config/registries/legacy-guidance.json +26 -0
- package/dist/config/server-instructions.js +0 -21
- package/dist/config/skill-templates/planu-context-assets.md +3 -6
- package/dist/config/skill-templates/planu-implement.md +2 -2
- package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
- package/dist/config/skill-templates/planu-release.md +2 -7
- package/dist/config/skill-templates/planu-validate.md +3 -3
- package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
- package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
- package/dist/config/spec-templates/stripe-payments/template.json +5 -1
- package/dist/config/spec-templates/webhook-system/template.json +5 -1
- package/dist/config/subagent-templates/planu-challenger.md +4 -9
- package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
- package/dist/config/subagent-templates/planu-validator.md +5 -8
- package/dist/config/tool-groups.json +6 -144
- package/dist/engine/ambiguity-scorer.js +40 -1
- package/dist/engine/autopilot/bootstrap.js +1 -1
- package/dist/engine/code-impact-analyzer.js +2 -6
- package/dist/engine/context-orchestrator/index.js +2 -2
- package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
- package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
- package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
- package/dist/engine/evidence-gates/artifact-reader.js +1 -1
- package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
- package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
- package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
- package/dist/engine/evidence-index/index-builder.js +21 -4
- package/dist/engine/execution/job-runtime.js +1 -1
- package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
- package/dist/engine/execution/package-handoff-job-executor.js +490 -0
- package/dist/engine/execution/validate-job-executor.js +20 -8
- package/dist/engine/git/exec-git.d.ts +4 -0
- package/dist/engine/git/exec-git.js +14 -0
- package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
- package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
- package/dist/engine/handoff-artifacts/schemas.js +1 -0
- package/dist/engine/handoff-format.d.ts +36 -0
- package/dist/engine/handoff-format.js +314 -0
- package/dist/engine/handoff-packager.d.ts +5 -3
- package/dist/engine/handoff-packager.js +89 -32
- package/dist/engine/host-tool-filter.js +20 -49
- package/dist/engine/human-summary.js +14 -3
- package/dist/engine/implementation-contract/common.d.ts +8 -3
- package/dist/engine/implementation-contract/common.js +9 -6
- package/dist/engine/implementation-contract/evaluator.js +4 -6
- package/dist/engine/implementation-contract/renderer.js +18 -23
- package/dist/engine/project-health-checker.js +2 -2
- package/dist/engine/readiness-checker.js +27 -0
- package/dist/engine/reconcile/apply-changes.js +4 -0
- package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
- package/dist/engine/reconcile/propagate-mirrors.js +311 -0
- package/dist/engine/self-healing/healer.js +12 -5
- package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
- package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
- package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
- package/dist/engine/self-healing/strategies/test-fix.js +32 -14
- package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
- package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
- package/dist/engine/session/checkpoint-writer.js +8 -4
- package/dist/engine/session/session-tracker.d.ts +5 -16
- package/dist/engine/session/session-tracker.js +29 -26
- package/dist/engine/session-state/writer.js +3 -11
- package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
- package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
- package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
- package/dist/engine/spec-format/bdd-parser.js +40 -11
- package/dist/engine/spec-format/lean-spec-generator.js +30 -10
- package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
- package/dist/engine/spec-format/retired-scaffold.js +5 -0
- package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
- package/dist/engine/spec-format/technical-md-populator.js +27 -0
- package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
- package/dist/engine/spec-format/unified-spec-builder.js +30 -20
- package/dist/engine/spec-grounding/contract.js +115 -1
- package/dist/engine/spec-migrator/fold-technical.js +60 -19
- package/dist/engine/spec-migrator/placeholder-technical.d.ts +2 -0
- package/dist/engine/spec-migrator/placeholder-technical.js +18 -0
- package/dist/engine/spec-quality/generic-output-gate.js +37 -3
- package/dist/engine/technical-enricher/index.js +4 -1
- package/dist/engine/type-safety-gate.js +3 -11
- package/dist/engine/universal-rules/catalog.js +0 -2
- package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
- package/dist/engine/validation/durable-validation.d.ts +3 -2
- package/dist/engine/validation/durable-validation.js +106 -40
- package/dist/engine/validation/validation-freshness.d.ts +3 -2
- package/dist/engine/validation/validation-freshness.js +22 -16
- package/dist/engine/validation/validation-worktree.js +34 -0
- package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
- package/dist/index.js +3 -17
- package/dist/resources/process.js +38 -65
- package/dist/storage/session-state-store.js +2 -0
- package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
- package/dist/tools/configure-checkpoint-policy.js +3 -3
- package/dist/tools/create-spec-helpers.js +2 -5
- package/dist/tools/create-spec.js +21 -4
- package/dist/tools/facilitate.js +3 -6
- package/dist/tools/git/branch-ops.js +16 -4
- package/dist/tools/github-release-handler.js +1 -20
- package/dist/tools/heal-spec-docs.js +1 -13
- package/dist/tools/init-project/agents-md-writer.js +6 -6
- package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
- package/dist/tools/init-project/claude-md-generator.js +0 -35
- package/dist/tools/init-project/handler.js +24 -49
- package/dist/tools/init-project/legacy-guidance-scanner.d.ts +12 -0
- package/dist/tools/init-project/legacy-guidance-scanner.js +176 -0
- package/dist/tools/init-project/per-client-files-writer.js +5 -5
- package/dist/tools/init-project/planu-workflow-generator.js +2 -13
- package/dist/tools/init-project/portable-index-reconciler.d.ts +21 -1
- package/dist/tools/init-project/portable-index-reconciler.js +66 -0
- package/dist/tools/init-project/rules-generator.d.ts +0 -11
- package/dist/tools/init-project/rules-generator.js +4 -85
- package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
- package/dist/tools/init-project/scaffold-writer.js +1 -12
- package/dist/tools/jobs/handlers.d.ts +0 -6
- package/dist/tools/jobs/handlers.js +13 -40
- package/dist/tools/list-specs.js +6 -21
- package/dist/tools/package-handoff.d.ts +3 -2
- package/dist/tools/package-handoff.js +153 -427
- package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
- package/dist/tools/register-sdd-tools.js +0 -23
- package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/output-schemas.d.ts +1 -18
- package/dist/tools/schemas/output-schemas.js +1 -11
- package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
- package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
- package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
- package/dist/tools/schemas/validate-output-schema.js +3 -2
- package/dist/tools/session-checkpoint.js +2 -2
- package/dist/tools/skill-registry/index.d.ts +0 -1
- package/dist/tools/skill-registry/index.js +0 -1
- package/dist/tools/sync-spec-state-handler.d.ts +2 -2
- package/dist/tools/sync-spec-state-handler.js +23 -3
- package/dist/tools/tool-registry/core-tools.js +1 -140
- package/dist/tools/tool-registry/group-infra.js +2 -39
- package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
- package/dist/tools/update-status/batch.js +17 -0
- package/dist/tools/update-status/dod-gates.js +14 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
- package/dist/tools/update-status/done-receipt-verifier.js +56 -26
- package/dist/tools/update-status/evidence-gate.js +10 -21
- package/dist/tools/update-status/file-sync.d.ts +1 -0
- package/dist/tools/update-status/file-sync.js +6 -0
- package/dist/tools/update-status/index.d.ts +6 -0
- package/dist/tools/update-status/index.js +39 -44
- package/dist/tools/update-status-actions.js +2 -7
- package/dist/tools/validate.js +34 -28
- package/dist/transports/oauth-validator.js +12 -1
- package/dist/transports/transport-factory.d.ts +2 -1
- package/dist/transports/transport-factory.js +19 -0
- package/dist/types/common/primitives.d.ts +2 -0
- package/dist/types/durable-job.d.ts +45 -0
- package/dist/types/durable-validation.d.ts +1 -1
- package/dist/types/evidence-autofill.d.ts +13 -1
- package/dist/types/handoff-artifacts.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/legacy-guidance.d.ts +20 -0
- package/dist/types/legacy-guidance.js +3 -0
- package/dist/types/readiness.d.ts +8 -1
- package/dist/types/reconcile.d.ts +6 -0
- package/dist/types/skill-registry.d.ts +26 -1
- package/dist/types/spec/core.d.ts +5 -0
- package/dist/types/spec/inputs.d.ts +2 -2
- package/dist/types/spec-format.d.ts +11 -1
- package/dist/types/spec-grounding.d.ts +12 -0
- package/dist/types/ssr-migration.d.ts +1 -1
- package/dist/types/transport.d.ts +1 -0
- package/dist/types/validation-receipt.d.ts +27 -0
- package/package.json +3 -2
- package/planu-plugin.json +13 -26
- package/src/i18n/messages/en.json +1 -1
- package/src/i18n/messages/es.json +1 -1
- package/src/i18n/messages/pt.json +1 -1
- package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
- package/dist/engine/skill-generator/conventions-hasher.js +0 -24
- package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
- package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
- package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
- package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
- package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
- package/dist/tools/clarify-requirements/questions-context.js +0 -74
- package/dist/tools/clarify-requirements/questions.d.ts +0 -16
- package/dist/tools/clarify-requirements/questions.js +0 -96
- package/dist/tools/clarify-requirements.d.ts +0 -4
- package/dist/tools/clarify-requirements.js +0 -314
- package/dist/tools/code-graph-handler.d.ts +0 -6
- package/dist/tools/code-graph-handler.js +0 -72
- package/dist/tools/create-rule.d.ts +0 -4
- package/dist/tools/create-rule.js +0 -107
- package/dist/tools/feedback-handler.d.ts +0 -7
- package/dist/tools/feedback-handler.js +0 -150
- package/dist/tools/semantic-search-handler.d.ts +0 -7
- package/dist/tools/semantic-search-handler.js +0 -71
- package/dist/tools/skill-registry/search.d.ts +0 -11
- 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
84
|
+
/** Public MCP boundary: submit the handoff job and acknowledge without packaging inline. */
|
|
326
85
|
export async function handlePackageHandoff(args) {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
341
|
-
|
|
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
|
-
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
370
|
-
|
|
147
|
+
finally {
|
|
148
|
+
__disposeResources(env_1);
|
|
371
149
|
}
|
|
372
150
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
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';
|