@planu/cli 5.3.1 → 5.3.3
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 +32 -0
- package/README.md +1 -1
- package/dist/config/official-sdd-tools.d.ts +1 -1
- package/dist/config/official-sdd-tools.js +1 -0
- package/dist/config/registries/hosts/codex.json +2 -1
- package/dist/config/runtime-policy.json +9 -0
- package/dist/engine/autopilot/complete-loop.js +4 -1
- package/dist/engine/execution/outbox-worker.d.ts +8 -0
- package/dist/engine/execution/outbox-worker.js +102 -31
- package/dist/engine/handoff-packager.d.ts +3 -0
- package/dist/engine/handoff-packager.js +18 -3
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +4 -4
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +4 -4
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +4 -4
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +4 -4
- package/dist/engine/runtime-policy.js +14 -0
- package/dist/index.js +3 -1
- package/dist/storage/runtime-db.d.ts +23 -1
- package/dist/storage/runtime-db.js +79 -9
- package/dist/tools/create-spec/post-creation.js +7 -2
- package/dist/tools/create-spec/spec-created-outbox-consumer.d.ts +2 -0
- package/dist/tools/create-spec/spec-created-outbox-consumer.js +94 -6
- package/dist/tools/create-spec-helpers.d.ts +2 -2
- package/dist/tools/create-spec-helpers.js +2 -2
- package/dist/tools/git/branch-ops.d.ts +7 -3
- package/dist/tools/git/branch-ops.js +68 -18
- package/dist/tools/package-handoff.d.ts +2 -1
- package/dist/tools/package-handoff.js +23 -4
- package/dist/tools/register-sdd-tools.js +2 -0
- package/dist/tools/update-status-actions.js +4 -2
- package/dist/types/git.d.ts +4 -0
- package/dist/types/outbox-worker.d.ts +2 -0
- package/dist/types/readiness.d.ts +1 -0
- package/dist/types/runtime-policy.d.ts +9 -0
- package/package.json +9 -9
- package/planu-native.json +1 -1
- package/planu-plugin.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
## [5.3.3] - 2026-08-06
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(release): tolerate empty ff-behind mirror list under set -u
|
|
5
|
+
- fix(outbox): return a typed failure from delivery attempts for the reliability gate
|
|
6
|
+
- fix(git): close SPEC-1396 debate findings and record review evidence
|
|
7
|
+
- fix(git): never move the shared checkout from automatic paths (SPEC-1396)
|
|
8
|
+
- fix(outbox): address SPEC-1271 debate review findings
|
|
9
|
+
- fix(outbox): bounded fair durable spec.created recovery without blocking startup (SPEC-1271)
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
- chore(planu): file SPEC-1396 git-safety dogfood bug
|
|
13
|
+
- chore(planu): session checkpoint after SPEC-1271 cycle
|
|
14
|
+
- chore(planu): SPEC-1271 done with debate evidence, file SPEC-1395
|
|
15
|
+
- chore(planu): SPEC-1271 implementing with structured work plan, file SPEC-1394
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## [5.3.2] - 2026-08-06
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
- fix(tools): restore declared bump_spec_version runtime tool (SPEC-1254)
|
|
22
|
+
- fix(handoff): address SPEC-1255 debate review findings
|
|
23
|
+
- fix(handoff): allow test-only specs to produce unblocked handoffs (SPEC-1255)
|
|
24
|
+
|
|
25
|
+
### Chores
|
|
26
|
+
- chore(planu): redact local paths from SPEC-1392 reproducer
|
|
27
|
+
- chore(planu): SPEC-1254 and SPEC-1255 done with debate-review evidence
|
|
28
|
+
- chore(planu): session checkpoint for debate-protocol batch
|
|
29
|
+
- chore(planu): approve SPEC-1254/1255/1271, file SPEC-1386..1391, add debate-review rule
|
|
30
|
+
- chore(planu): approve SPEC-1206 and SPEC-1250 with reviewer evidence, SPEC-1240 rework feedback
|
|
31
|
+
|
|
32
|
+
|
|
1
33
|
## [5.3.1] - 2026-08-05
|
|
2
34
|
|
|
3
35
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Planu is a Spec Driven Development stack for AI coding agents: an MCP server and
|
|
|
21
21
|
- **Local change detection** — observes project changes without requiring a hosted configuration service.
|
|
22
22
|
- **Unified Spec Format** — Requirements and technical details in a single, context-efficient `spec.md`.
|
|
23
23
|
- **Atomic Integrity** — Indestructible writes and **Freeze-on-done** immutability (P0).
|
|
24
|
-
- **
|
|
24
|
+
- **29 official MCP tools** for the public SDD surface, with advanced workflows kept in skills, CLI commands, and internal pipelines.
|
|
25
25
|
- **Multi-agent compatible** — Optimized for Claude Code, Cursor, Windsurf, and Gemini CLI.
|
|
26
26
|
|
|
27
27
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Single editable authority for the official local SDD tool surface. */
|
|
2
|
-
export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ['planu_status', 'session_checkpoint', 'facilitate', 'init_project', 'clarify_requirements', 'create_spec', 'list_specs', 'challenge_spec', 'check_readiness', 'update_status', 'update_status_batch', 'package_handoff', 'validate', 'get_job', 'watch_job', 'cancel_job', 'restart_job', 'reconcile_spec', 'create_rule', 'create_skill', 'skill_search', 'configure_code_graph', 'code_graph_status', 'submit_feedback', 'triage_feedback', 'resolve_feedback', 'sync_feedback', 'feedback_status'];
|
|
2
|
+
export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ['planu_status', 'session_checkpoint', 'facilitate', 'init_project', 'clarify_requirements', 'create_spec', 'list_specs', 'challenge_spec', 'check_readiness', 'update_status', 'update_status_batch', 'package_handoff', 'validate', 'get_job', 'watch_job', 'cancel_job', 'restart_job', 'reconcile_spec', 'create_rule', 'create_skill', 'skill_search', 'configure_code_graph', 'code_graph_status', 'submit_feedback', 'triage_feedback', 'resolve_feedback', 'sync_feedback', 'feedback_status', 'bump_spec_version'];
|
|
3
3
|
export declare function readCanonicalToolNames(): string[];
|
|
4
4
|
//# sourceMappingURL=official-sdd-tools.d.ts.map
|
|
@@ -51,5 +51,14 @@
|
|
|
51
51
|
"leaseMultiplier": 3,
|
|
52
52
|
"maximumRememberedTransitions": 1000,
|
|
53
53
|
"minimumHeartbeatMs": 1000
|
|
54
|
+
},
|
|
55
|
+
"outbox": {
|
|
56
|
+
"sliceRecordBudget": 25,
|
|
57
|
+
"sliceTimeBudgetMs": 5000,
|
|
58
|
+
"backoffBaseMs": 30000,
|
|
59
|
+
"backoffCapMs": 1800000,
|
|
60
|
+
"maxAttempts": 3,
|
|
61
|
+
"diagnosticMaxLength": 500,
|
|
62
|
+
"continuationDelayMs": 250
|
|
54
63
|
}
|
|
55
64
|
}
|
|
@@ -47,7 +47,10 @@ async function runWithProgress(projectPath, projectId, specId, action, fn) {
|
|
|
47
47
|
*/
|
|
48
48
|
async function doAutoCreateBranch(projectId, specId) {
|
|
49
49
|
const { handleCreateBranch } = await import('../../tools/git/branch-ops.js');
|
|
50
|
-
|
|
50
|
+
// SPEC-1396: cascade side effect — create the ref, never move the shared checkout.
|
|
51
|
+
const result = await handleCreateBranch(projectId, specId, undefined, undefined, {
|
|
52
|
+
mutateCheckout: false,
|
|
53
|
+
});
|
|
51
54
|
if (result.isError === true) {
|
|
52
55
|
/* v8 ignore next */
|
|
53
56
|
const msg = result.content[0]?.type === 'text' ? result.content[0].text : 'branch creation failed';
|
|
@@ -18,12 +18,20 @@ export declare class TransactionalOutboxWorker {
|
|
|
18
18
|
readonly clock?: () => Date;
|
|
19
19
|
readonly limit?: number;
|
|
20
20
|
readonly topics?: readonly string[];
|
|
21
|
+
readonly timeBudgetMs?: number;
|
|
21
22
|
}): Promise<{
|
|
22
23
|
readonly delivered: number;
|
|
24
|
+
readonly failed: number;
|
|
23
25
|
readonly skipped: number;
|
|
26
|
+
readonly ineligible: number;
|
|
27
|
+
/** True when no eligible pending record remains in this scope right now. */
|
|
28
|
+
readonly exhausted: boolean;
|
|
24
29
|
}>;
|
|
30
|
+
private attemptDelivery;
|
|
25
31
|
private claim;
|
|
26
32
|
private fail;
|
|
33
|
+
/** Deterministic exponential backoff with a validated cap; no random jitter. */
|
|
34
|
+
private computeNextAttemptAt;
|
|
27
35
|
private complete;
|
|
28
36
|
private deliveryKey;
|
|
29
37
|
private fenceKey;
|
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { BoundaryFailure } from '../../errors/error-taxonomy.js';
|
|
3
|
+
import { getRuntimePolicy } from '../runtime-policy.js';
|
|
3
4
|
function parseFence(value) {
|
|
4
5
|
if (!value || typeof value !== 'object') {
|
|
5
6
|
return undefined;
|
|
6
7
|
}
|
|
7
8
|
const fence = value;
|
|
9
|
+
const nextAttemptAtValid = fence.nextAttemptAt === undefined ||
|
|
10
|
+
(typeof fence.nextAttemptAt === 'string' && Number.isFinite(Date.parse(fence.nextAttemptAt)));
|
|
8
11
|
if (fence.schemaVersion !== 1 ||
|
|
9
12
|
typeof fence.workerId !== 'string' ||
|
|
10
13
|
!Number.isSafeInteger(fence.fencingToken) ||
|
|
11
14
|
typeof fence.leaseExpiresAt !== 'string' ||
|
|
12
15
|
typeof fence.state !== 'string' ||
|
|
13
|
-
!['leased', 'failed', 'dead-letter', 'delivered'].includes(fence.state)
|
|
16
|
+
!['leased', 'failed', 'dead-letter', 'delivered'].includes(fence.state) ||
|
|
17
|
+
!nextAttemptAtValid) {
|
|
14
18
|
throw new BoundaryFailure('Corrupt', 'outbox fence read', 'Quarantine the invalid fence record before retrying delivery.');
|
|
15
19
|
}
|
|
16
20
|
return fence;
|
|
17
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Upper bound on how many rows a single internal page fetch may request while
|
|
24
|
+
* paging past ineligible head records (foreign lease, delivered self-heal) that
|
|
25
|
+
* consume no record budget. 1000 is the existing hard ceiling `listOutbox`
|
|
26
|
+
* already enforces, so this reuses that ceiling rather than introducing a new
|
|
27
|
+
* tunable.
|
|
28
|
+
*/
|
|
29
|
+
const OUTBOX_PAGE_FETCH_LIMIT = 1_000;
|
|
18
30
|
/**
|
|
19
31
|
* Durable outbox dispatcher. The sink must deduplicate `deliveryKey`; this stable key closes the
|
|
20
32
|
* unavoidable crash window between an external acknowledgement and the local delivered marker.
|
|
@@ -26,7 +38,7 @@ export class TransactionalOutboxWorker {
|
|
|
26
38
|
workerId;
|
|
27
39
|
leaseMs;
|
|
28
40
|
maxAttempts;
|
|
29
|
-
constructor(database, projectId, workspaceId, workerId, leaseMs = 30_000, maxAttempts =
|
|
41
|
+
constructor(database, projectId, workspaceId, workerId, leaseMs = 30_000, maxAttempts = getRuntimePolicy().outbox.maxAttempts) {
|
|
30
42
|
this.database = database;
|
|
31
43
|
this.projectId = projectId;
|
|
32
44
|
this.workspaceId = workspaceId;
|
|
@@ -46,38 +58,83 @@ export class TransactionalOutboxWorker {
|
|
|
46
58
|
async drain(deliver, options = {}) {
|
|
47
59
|
const clock = options.clock ?? (() => new Date());
|
|
48
60
|
const now = currentIso(clock, options.now);
|
|
61
|
+
const policy = getRuntimePolicy().outbox;
|
|
62
|
+
const timeBudgetMs = options.timeBudgetMs ?? policy.sliceTimeBudgetMs;
|
|
63
|
+
const startedAtMs = clock().getTime();
|
|
64
|
+
let recordBudget = options.limit ?? policy.sliceRecordBudget;
|
|
49
65
|
let delivered = 0;
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
deliveryKey: this.deliveryKey(message),
|
|
65
|
-
fencingToken: fence.fencingToken,
|
|
66
|
-
topic: message.topic,
|
|
67
|
-
payload: message.payload,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
const failure = new BoundaryFailure('DependencyUnavailable', 'outbox delivery', 'Retry with the stable delivery key after the dependency recovers.');
|
|
72
|
-
this.fail(message, fence, failure, currentIso(clock, options.now));
|
|
73
|
-
throw failure;
|
|
66
|
+
let failed = 0;
|
|
67
|
+
let ineligible = 0;
|
|
68
|
+
let cursorId = 0;
|
|
69
|
+
let timedOut = false;
|
|
70
|
+
while (recordBudget > 0 && !timedOut) {
|
|
71
|
+
const batch = this.database.listOutbox(this.projectId, this.workspaceId, {
|
|
72
|
+
pendingOnly: true,
|
|
73
|
+
limit: OUTBOX_PAGE_FETCH_LIMIT,
|
|
74
|
+
topics: options.topics,
|
|
75
|
+
eligibleAt: now,
|
|
76
|
+
afterId: cursorId,
|
|
77
|
+
});
|
|
78
|
+
if (batch.length === 0) {
|
|
79
|
+
break;
|
|
74
80
|
}
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
for (const message of batch) {
|
|
82
|
+
// Admission check between deliveries: never aborts an in-flight delivery.
|
|
83
|
+
if (clock().getTime() - startedAtMs > timeBudgetMs) {
|
|
84
|
+
timedOut = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
cursorId = message.id;
|
|
88
|
+
const fence = this.claim(message, now);
|
|
89
|
+
if (!fence) {
|
|
90
|
+
// Ineligible for a reason SQL cannot pre-filter (foreign unexpired lease,
|
|
91
|
+
// self-healed delivered fence): consumes no record budget and does not
|
|
92
|
+
// stop the worker from advancing to the next record in this same call.
|
|
93
|
+
ineligible += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
recordBudget -= 1;
|
|
97
|
+
const outcome = await this.attemptDelivery(message, fence, deliver, clock, options.now);
|
|
98
|
+
if (outcome.status === 'failed') {
|
|
99
|
+
// Isolate the failure: continue offering remaining eligible records
|
|
100
|
+
// instead of aborting the whole slice.
|
|
101
|
+
failed += 1;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
delivered += 1;
|
|
105
|
+
}
|
|
106
|
+
if (recordBudget === 0) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
77
109
|
}
|
|
78
|
-
delivered += 1;
|
|
79
110
|
}
|
|
80
|
-
|
|
111
|
+
// Exact count of eligible work left unattempted (budget or time cutoff), not
|
|
112
|
+
// an estimate — this is what a further slice would find eligible right now.
|
|
113
|
+
const skipped = this.database.countEligibleOutbox(this.projectId, this.workspaceId, {
|
|
114
|
+
topics: options.topics,
|
|
115
|
+
eligibleAt: now,
|
|
116
|
+
afterId: cursorId,
|
|
117
|
+
});
|
|
118
|
+
return { delivered, failed, skipped, ineligible, exhausted: skipped === 0 };
|
|
119
|
+
}
|
|
120
|
+
async attemptDelivery(message, fence, deliver, clock, now) {
|
|
121
|
+
try {
|
|
122
|
+
await deliver({
|
|
123
|
+
deliveryKey: this.deliveryKey(message),
|
|
124
|
+
fencingToken: fence.fencingToken,
|
|
125
|
+
topic: message.topic,
|
|
126
|
+
payload: message.payload,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
const failure = new BoundaryFailure('DependencyUnavailable', 'outbox delivery', 'Retry with the stable delivery key after the dependency recovers.');
|
|
131
|
+
this.fail(message, fence, failure, currentIso(clock, now));
|
|
132
|
+
return { status: 'failed' };
|
|
133
|
+
}
|
|
134
|
+
if (!this.complete(message, fence, currentIso(clock, now))) {
|
|
135
|
+
throw new BoundaryFailure('Conflict', 'outbox delivery commit', 'Another worker owns the lease; retry with the stable delivery key.');
|
|
136
|
+
}
|
|
137
|
+
return { status: 'delivered' };
|
|
81
138
|
}
|
|
82
139
|
claim(message, now) {
|
|
83
140
|
return this.database.transaction(() => {
|
|
@@ -93,6 +150,12 @@ export class TransactionalOutboxWorker {
|
|
|
93
150
|
if (existing?.state === 'leased' && existing.leaseExpiresAt > now) {
|
|
94
151
|
return undefined;
|
|
95
152
|
}
|
|
153
|
+
if (existing?.state === 'failed' &&
|
|
154
|
+
existing.nextAttemptAt !== undefined &&
|
|
155
|
+
existing.nextAttemptAt > now) {
|
|
156
|
+
// Durable retry deadline not yet reached: not eligible, attempts unchanged.
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
96
159
|
const fence = {
|
|
97
160
|
schemaVersion: 1,
|
|
98
161
|
workerId: this.workerId,
|
|
@@ -126,6 +189,7 @@ export class TransactionalOutboxWorker {
|
|
|
126
189
|
return false;
|
|
127
190
|
}
|
|
128
191
|
const attempts = current.attempts ?? 1;
|
|
192
|
+
const deadLettered = attempts >= this.maxAttempts;
|
|
129
193
|
this.database.putRecord({
|
|
130
194
|
projectId: this.projectId,
|
|
131
195
|
workspaceId: this.workspaceId,
|
|
@@ -133,8 +197,9 @@ export class TransactionalOutboxWorker {
|
|
|
133
197
|
key,
|
|
134
198
|
value: {
|
|
135
199
|
...current,
|
|
136
|
-
state:
|
|
200
|
+
state: deadLettered ? 'dead-letter' : 'failed',
|
|
137
201
|
leaseExpiresAt: now,
|
|
202
|
+
...(deadLettered ? {} : { nextAttemptAt: this.computeNextAttemptAt(now, attempts) }),
|
|
138
203
|
lastError: error instanceof BoundaryFailure
|
|
139
204
|
? error.message
|
|
140
205
|
: new BoundaryFailure('Internal', 'outbox failure recording', 'Inspect local diagnostics and retry safely.').message,
|
|
@@ -144,6 +209,12 @@ export class TransactionalOutboxWorker {
|
|
|
144
209
|
return true;
|
|
145
210
|
});
|
|
146
211
|
}
|
|
212
|
+
/** Deterministic exponential backoff with a validated cap; no random jitter. */
|
|
213
|
+
computeNextAttemptAt(now, attempts) {
|
|
214
|
+
const policy = getRuntimePolicy().outbox;
|
|
215
|
+
const delayMs = Math.min(policy.backoffBaseMs * 2 ** (attempts - 1), policy.backoffCapMs);
|
|
216
|
+
return new Date(Date.parse(now) + delayMs).toISOString();
|
|
217
|
+
}
|
|
147
218
|
complete(message, fence, now) {
|
|
148
219
|
return this.database.transaction(() => {
|
|
149
220
|
const key = this.fenceKey(message.id);
|
|
@@ -16,6 +16,9 @@ export declare function extractCanonicalFileOwnership(content: string): {
|
|
|
16
16
|
toModify: string[];
|
|
17
17
|
toCreate: string[];
|
|
18
18
|
toTest: string[];
|
|
19
|
+
/** Test paths as declared, before conflict filtering — used to exclude conflicted
|
|
20
|
+
* paths from other categories even though they no longer appear in `toTest`. */
|
|
21
|
+
toTestDeclared: string[];
|
|
19
22
|
blockers: string[];
|
|
20
23
|
};
|
|
21
24
|
export declare function packageHandoff(spec: Spec, knowledge: ProjectKnowledge): Promise<HandoffPackage>;
|
|
@@ -174,7 +174,14 @@ export function normalizeChangedFilePath(value) {
|
|
|
174
174
|
export function extractCanonicalFileOwnership(content) {
|
|
175
175
|
const filesSection = sectionContentAtLevel(content, 'Files', 2);
|
|
176
176
|
if (filesSection === null) {
|
|
177
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
present: false,
|
|
179
|
+
toModify: [],
|
|
180
|
+
toCreate: [],
|
|
181
|
+
toTest: [],
|
|
182
|
+
toTestDeclared: [],
|
|
183
|
+
blockers: [],
|
|
184
|
+
};
|
|
178
185
|
}
|
|
179
186
|
const blockers = [];
|
|
180
187
|
const addBlocker = (blocker) => {
|
|
@@ -215,6 +222,7 @@ export function extractCanonicalFileOwnership(content) {
|
|
|
215
222
|
toCreate: toCreate.filter((path) => !conflicts.includes(path)),
|
|
216
223
|
toModify: toModify.filter((path) => !conflicts.includes(path)),
|
|
217
224
|
toTest: toTest.filter((path) => !conflicts.includes(path)),
|
|
225
|
+
toTestDeclared: toTest,
|
|
218
226
|
blockers,
|
|
219
227
|
};
|
|
220
228
|
}
|
|
@@ -541,7 +549,7 @@ export async function packageHandoff(spec, knowledge) {
|
|
|
541
549
|
const canonicalFiles = extractCanonicalFileOwnership(rawSpecContent);
|
|
542
550
|
const canonicalOwnership = extractStrictCanonicalOwnership(ownershipContent);
|
|
543
551
|
const workPlanOwnership = extractFileLevelWorkPlanOwnership(ownershipContent);
|
|
544
|
-
const testOwnedPaths = new Set(canonicalFiles.
|
|
552
|
+
const testOwnedPaths = new Set(canonicalFiles.toTestDeclared);
|
|
545
553
|
const explicitOwnership = canonicalOwnership.present
|
|
546
554
|
? {
|
|
547
555
|
...canonicalOwnership,
|
|
@@ -574,7 +582,9 @@ export async function packageHandoff(spec, knowledge) {
|
|
|
574
582
|
}
|
|
575
583
|
const blockers = [
|
|
576
584
|
...(criteria.length === 0 ? ['Missing BDD acceptance criteria'] : []),
|
|
577
|
-
...(toModify.length + toCreate.length === 0
|
|
585
|
+
...(toModify.length + toCreate.length + canonicalFiles.toTest.length === 0
|
|
586
|
+
? ['Missing file ownership / target files']
|
|
587
|
+
: []),
|
|
578
588
|
...(operational.testPlan.length === 0 ? ['Missing test plan'] : []),
|
|
579
589
|
...(operational.risks.length === 0 ? ['Missing risk analysis'] : []),
|
|
580
590
|
...(operational.ownership.length === 0 ? ['Missing ownership'] : []),
|
|
@@ -594,6 +604,7 @@ export async function packageHandoff(spec, knowledge) {
|
|
|
594
604
|
criteria,
|
|
595
605
|
filesToModify: toModify,
|
|
596
606
|
filesToCreate: toCreate,
|
|
607
|
+
filesToTest: canonicalFiles.toTest,
|
|
597
608
|
ownership: operational.ownership,
|
|
598
609
|
testPlan: operational.testPlan,
|
|
599
610
|
risks: operational.risks,
|
|
@@ -626,6 +637,10 @@ function renderPersistedHandoff(pkg) {
|
|
|
626
637
|
'## Files To Create',
|
|
627
638
|
...pkg.filesToCreate.map((filePath) => `- ${filePath}`),
|
|
628
639
|
'',
|
|
640
|
+
'## Test Files',
|
|
641
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- compat guard: callers may construct a HandoffPackage object predating this field
|
|
642
|
+
...(pkg.filesToTest ?? []).map((filePath) => `- ${filePath}`),
|
|
643
|
+
'',
|
|
629
644
|
'## Ownership',
|
|
630
645
|
...pkg.ownership.map((item) => `- ${item}`),
|
|
631
646
|
'',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.3",
|
|
4
|
+
"engineVersion": "5.3.3",
|
|
5
|
+
"buildId": "1-5.3.3-aarch64-apple-darwin",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"platform": "darwin",
|
|
18
18
|
"arch": "arm64",
|
|
19
19
|
"libc": null,
|
|
20
|
-
"artifactSha256": "
|
|
20
|
+
"artifactSha256": "936cf103c37c5622595eba08d503131657a4718d37b84d9355fe3dd8b63f313a",
|
|
21
21
|
"sbom": {
|
|
22
22
|
"format": "CycloneDX-1.5",
|
|
23
23
|
"path": "planu-core.darwin-arm64.node.sbom.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "a3350e8b345415b6b7881cd0ed7b1fa09a86e21530f790e08f93e74c0bfc4384"
|
|
25
25
|
},
|
|
26
26
|
"provenance": {
|
|
27
27
|
"builder": "scripts/build-rust-local.sh",
|
|
28
|
-
"sourceCommit": "
|
|
28
|
+
"sourceCommit": "30eda7a2ba11647e835b0079c526bed77f189c8e",
|
|
29
29
|
"sourceTreeDirty": false,
|
|
30
30
|
"sourceDateEpoch": 1,
|
|
31
31
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"signature": {
|
|
35
35
|
"algorithm": "Ed25519",
|
|
36
36
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
37
|
-
"value": "
|
|
37
|
+
"value": "bk6W0MZRvTB3XJtJPw0Oe9Brcs8vOa0ZyWNyx81gBZHTp1qRjQjyT+bWVWjMRl9ULBE8+Iu5Sd02wR0ZGZ3MCQ=="
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"version": 1,
|
|
5
5
|
"metadata": {
|
|
6
6
|
"component": {
|
|
7
|
-
"bom-ref": "pkg:cargo/planu-core@5.3.
|
|
7
|
+
"bom-ref": "pkg:cargo/planu-core@5.3.3",
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "planu-core",
|
|
10
|
-
"version": "5.3.
|
|
10
|
+
"version": "5.3.3",
|
|
11
11
|
"hashes": [
|
|
12
12
|
{
|
|
13
13
|
"alg": "SHA-256",
|
|
14
|
-
"content": "
|
|
14
|
+
"content": "936cf103c37c5622595eba08d503131657a4718d37b84d9355fe3dd8b63f313a"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
"dependsOn": []
|
|
1107
1107
|
},
|
|
1108
1108
|
{
|
|
1109
|
-
"ref": "pkg:cargo/planu-core@5.3.
|
|
1109
|
+
"ref": "pkg:cargo/planu-core@5.3.3",
|
|
1110
1110
|
"dependsOn": [
|
|
1111
1111
|
"pkg:cargo/core-foundation@0.10.1",
|
|
1112
1112
|
"pkg:cargo/hmac@0.12.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.3",
|
|
4
|
+
"engineVersion": "5.3.3",
|
|
5
|
+
"buildId": "1-5.3.3-x86_64-apple-darwin",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"platform": "darwin",
|
|
18
18
|
"arch": "x64",
|
|
19
19
|
"libc": null,
|
|
20
|
-
"artifactSha256": "
|
|
20
|
+
"artifactSha256": "a00b4de15350f55e6b2dfbff7d172fe9c731f857b517ca903a3ef2faab5c9b2a",
|
|
21
21
|
"sbom": {
|
|
22
22
|
"format": "CycloneDX-1.5",
|
|
23
23
|
"path": "planu-core.darwin-x64.node.sbom.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "7e25d45c059cf37c0f32d13526d9857d8b33e20e1326428d2ce6f6bf2684029b"
|
|
25
25
|
},
|
|
26
26
|
"provenance": {
|
|
27
27
|
"builder": "scripts/build-rust-local.sh",
|
|
28
|
-
"sourceCommit": "
|
|
28
|
+
"sourceCommit": "30eda7a2ba11647e835b0079c526bed77f189c8e",
|
|
29
29
|
"sourceTreeDirty": false,
|
|
30
30
|
"sourceDateEpoch": 1,
|
|
31
31
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"signature": {
|
|
35
35
|
"algorithm": "Ed25519",
|
|
36
36
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
37
|
-
"value": "
|
|
37
|
+
"value": "JFfOtvnI+70+fijN8e5fJF4XqkwdZyAR8tOUxm+gdnM1okUI0EFUtqnb2nzPFzG4RnFZGSYgnoYJQpfd5zmnDw=="
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"version": 1,
|
|
5
5
|
"metadata": {
|
|
6
6
|
"component": {
|
|
7
|
-
"bom-ref": "pkg:cargo/planu-core@5.3.
|
|
7
|
+
"bom-ref": "pkg:cargo/planu-core@5.3.3",
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "planu-core",
|
|
10
|
-
"version": "5.3.
|
|
10
|
+
"version": "5.3.3",
|
|
11
11
|
"hashes": [
|
|
12
12
|
{
|
|
13
13
|
"alg": "SHA-256",
|
|
14
|
-
"content": "
|
|
14
|
+
"content": "a00b4de15350f55e6b2dfbff7d172fe9c731f857b517ca903a3ef2faab5c9b2a"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
"dependsOn": []
|
|
1107
1107
|
},
|
|
1108
1108
|
{
|
|
1109
|
-
"ref": "pkg:cargo/planu-core@5.3.
|
|
1109
|
+
"ref": "pkg:cargo/planu-core@5.3.3",
|
|
1110
1110
|
"dependsOn": [
|
|
1111
1111
|
"pkg:cargo/core-foundation@0.10.1",
|
|
1112
1112
|
"pkg:cargo/hmac@0.12.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.3",
|
|
4
|
+
"engineVersion": "5.3.3",
|
|
5
|
+
"buildId": "1-5.3.3-aarch64-unknown-linux-gnu",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"platform": "linux",
|
|
17
17
|
"arch": "arm64",
|
|
18
18
|
"libc": "glibc",
|
|
19
|
-
"artifactSha256": "
|
|
19
|
+
"artifactSha256": "83c55f2f9e0f2bc32bca52de4d5ed248b09217d1893d2e05ce7880394e25a884",
|
|
20
20
|
"sbom": {
|
|
21
21
|
"format": "CycloneDX-1.5",
|
|
22
22
|
"path": "planu-core.linux-arm64-gnu.node.sbom.json",
|
|
23
|
-
"sha256": "
|
|
23
|
+
"sha256": "39ddcdd6a6c9fae71bbb1f6f1716a1352ee37bf81196b24eef318d4abb0b4b1d"
|
|
24
24
|
},
|
|
25
25
|
"provenance": {
|
|
26
26
|
"builder": "scripts/build-rust-local.sh",
|
|
27
|
-
"sourceCommit": "
|
|
27
|
+
"sourceCommit": "30eda7a2ba11647e835b0079c526bed77f189c8e",
|
|
28
28
|
"sourceTreeDirty": false,
|
|
29
29
|
"sourceDateEpoch": 1,
|
|
30
30
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"signature": {
|
|
34
34
|
"algorithm": "Ed25519",
|
|
35
35
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
36
|
-
"value": "
|
|
36
|
+
"value": "P1xQ4p3pp++gck7GGKW/Ue9/MdpedjnqGeuyl/76jzqFN5VmtA+L/5jlHvBc+qDPGkNT6wiXY8R3J5CHkjk5Cg=="
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"version": 1,
|
|
5
5
|
"metadata": {
|
|
6
6
|
"component": {
|
|
7
|
-
"bom-ref": "pkg:cargo/planu-core@5.3.
|
|
7
|
+
"bom-ref": "pkg:cargo/planu-core@5.3.3",
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "planu-core",
|
|
10
|
-
"version": "5.3.
|
|
10
|
+
"version": "5.3.3",
|
|
11
11
|
"hashes": [
|
|
12
12
|
{
|
|
13
13
|
"alg": "SHA-256",
|
|
14
|
-
"content": "
|
|
14
|
+
"content": "83c55f2f9e0f2bc32bca52de4d5ed248b09217d1893d2e05ce7880394e25a884"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
"dependsOn": []
|
|
1107
1107
|
},
|
|
1108
1108
|
{
|
|
1109
|
-
"ref": "pkg:cargo/planu-core@5.3.
|
|
1109
|
+
"ref": "pkg:cargo/planu-core@5.3.3",
|
|
1110
1110
|
"dependsOn": [
|
|
1111
1111
|
"pkg:cargo/core-foundation@0.10.1",
|
|
1112
1112
|
"pkg:cargo/hmac@0.12.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.3",
|
|
4
|
+
"engineVersion": "5.3.3",
|
|
5
|
+
"buildId": "1-5.3.3-aarch64-unknown-linux-musl",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"platform": "linux",
|
|
17
17
|
"arch": "arm64",
|
|
18
18
|
"libc": "musl",
|
|
19
|
-
"artifactSha256": "
|
|
19
|
+
"artifactSha256": "6f31cf91ea1ae1bde34191ab4a7b7c2d6a58dcdb7206f18dcefcc1dfb42b349b",
|
|
20
20
|
"sbom": {
|
|
21
21
|
"format": "CycloneDX-1.5",
|
|
22
22
|
"path": "planu-core.linux-arm64-musl.node.sbom.json",
|
|
23
|
-
"sha256": "
|
|
23
|
+
"sha256": "c90938e79cf8fb28eb448b50b683c624c65ddfd34ee50515b3b999465d55cc83"
|
|
24
24
|
},
|
|
25
25
|
"provenance": {
|
|
26
26
|
"builder": "scripts/build-rust-local.sh",
|
|
27
|
-
"sourceCommit": "
|
|
27
|
+
"sourceCommit": "30eda7a2ba11647e835b0079c526bed77f189c8e",
|
|
28
28
|
"sourceTreeDirty": false,
|
|
29
29
|
"sourceDateEpoch": 1,
|
|
30
30
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"signature": {
|
|
34
34
|
"algorithm": "Ed25519",
|
|
35
35
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
36
|
-
"value": "
|
|
36
|
+
"value": "uvmtUXDibJr1Kf/Y1D1IiFea7pMuBcx2UWIonJHde2OweiQqQ1++KkoPMLGAIxfsSTncVTdXpU7nGiGpIm3jAQ=="
|
|
37
37
|
}
|
|
38
38
|
}
|