@abelxiaoxing/cadence 1.3.0 → 1.4.1
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/README.md +172 -13
- package/agents/design-explorer.md +17 -11
- package/agents/diagnosis-worker.md +9 -1
- package/agents/implementation-worker.md +3 -1
- package/config/plan-draft.example.json +122 -0
- package/config/plan-draft.multiple-tasks.example.json +345 -0
- package/package.json +9 -5
- package/prompts/abel-design.md +141 -38
- package/prompts/abel-diagnose.md +10 -1
- package/prompts/abel-implement.md +118 -20
- package/prompts/abel-init.md +9 -2
- package/src/activity-contracts.ts +82 -0
- package/src/apply-transaction.ts +16 -14
- package/src/artifact-store.ts +17 -0
- package/src/candidate-artifact.ts +143 -0
- package/src/candidate-patch.ts +33 -12
- package/src/candidate-workspace.ts +74 -0
- package/src/canonical.ts +16 -0
- package/src/change-contract-schema.ts +191 -0
- package/src/change-contract.ts +273 -0
- package/src/change-verification.ts +896 -0
- package/src/child-model.ts +129 -0
- package/src/child-session.ts +296 -328
- package/src/contracts.ts +325 -109
- package/src/control-contracts.ts +51 -1
- package/src/delivery-compiler.ts +241 -141
- package/src/delivery-revision.ts +42 -0
- package/src/design-control.ts +182 -15
- package/src/design-diagnostics.ts +150 -0
- package/src/design-journal.ts +216 -72
- package/src/durable-contracts.ts +210 -0
- package/src/durable-workflow.ts +963 -0
- package/src/evidence-draft.ts +122 -0
- package/src/implement-graph.ts +14 -16
- package/src/implement-plan.ts +70 -0
- package/src/index.ts +341 -1405
- package/src/isolation-backend.ts +84 -23
- package/src/model-source.ts +22 -0
- package/src/package-candidate.ts +224 -0
- package/src/package-delivery.ts +486 -0
- package/src/package-verification.ts +509 -0
- package/src/package-workflow.ts +253 -0
- package/src/packet-runtime.ts +27 -48
- package/src/parent-provider.ts +86 -198
- package/src/phase-execution.ts +2248 -0
- package/src/pi-adapter.ts +19 -0
- package/src/plan-draft-summary.ts +146 -0
- package/src/plan-draft.ts +493 -0
- package/src/route-policy.ts +1 -11
- package/src/run-state.ts +4 -16
- package/src/run-store.ts +5 -144
- package/src/scoped-grep-worker.mjs +23 -0
- package/src/scoped-tools.ts +312 -116
- package/src/sqlite-schema.ts +171 -0
- package/src/storage-schema.ts +352 -0
- package/src/subagent-activity.ts +46 -67
- package/src/submit-tool.ts +40 -61
- package/src/task-ledger.ts +14 -55
- package/src/transport-budget.ts +32 -0
- package/src/verification-capability.ts +298 -102
- package/src/verification-environment.ts +213 -0
- package/src/verification-identity.ts +77 -0
- package/src/worker-broker.ts +134 -91
- package/src/workflow-engine.ts +28 -8285
- package/src/workflow-policy.ts +1388 -0
- package/src/workflow-recovery-policy.ts +95 -0
- package/src/workflow-scheduling.ts +105 -0
- package/src/workflow-state-machine.ts +4086 -0
- package/src/workflow-status.ts +384 -0
- package/src/workspace-io-entry.mjs +58 -0
- package/src/workspace-io-worker.mjs +991 -0
- package/src/workspace-io.ts +84 -0
- package/src/workspace-store.ts +125 -19
- package/skills/abel-workflow/SKILL.md +0 -205
- package/src/empty-resource-loader.ts +0 -38
- package/src/parent-payload-bridge.ts +0 -523
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
export interface WorkspaceIoMetrics {
|
|
4
|
+
operation: "captureBaseline" | "materialize" | "verificationIdentity";
|
|
5
|
+
elapsedMs: number;
|
|
6
|
+
files: number;
|
|
7
|
+
bytes: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Trusted local I/O worker; it has no model, tools, or workflow authority. */
|
|
11
|
+
export async function runWorkspaceIo<T>(input: {
|
|
12
|
+
root: string;
|
|
13
|
+
artifactRoot: string;
|
|
14
|
+
operation: WorkspaceIoMetrics["operation"];
|
|
15
|
+
args: unknown[];
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
onMetrics?: (metrics: WorkspaceIoMetrics) => void;
|
|
18
|
+
}): Promise<T> {
|
|
19
|
+
input.signal?.throwIfAborted();
|
|
20
|
+
const cancelled = new SharedArrayBuffer(4);
|
|
21
|
+
const flag = new Int32Array(cancelled);
|
|
22
|
+
const started = performance.now();
|
|
23
|
+
const worker = new Worker(
|
|
24
|
+
new URL("./workspace-io-worker.mjs", import.meta.url),
|
|
25
|
+
{
|
|
26
|
+
workerData: {
|
|
27
|
+
root: input.root,
|
|
28
|
+
artifactRoot: input.artifactRoot,
|
|
29
|
+
operation: input.operation,
|
|
30
|
+
args: input.args,
|
|
31
|
+
cancelled,
|
|
32
|
+
},
|
|
33
|
+
// The shipped worker is pure JavaScript, including when installed under node_modules.
|
|
34
|
+
execArgv: [],
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
const abort = () => {
|
|
38
|
+
Atomics.store(flag, 0, 1);
|
|
39
|
+
worker.postMessage("cancel");
|
|
40
|
+
};
|
|
41
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
42
|
+
if (input.signal?.aborted) abort();
|
|
43
|
+
try {
|
|
44
|
+
return await new Promise<T>((resolve, reject) => {
|
|
45
|
+
let message:
|
|
46
|
+
| {
|
|
47
|
+
ok: boolean;
|
|
48
|
+
result?: T;
|
|
49
|
+
error?: string;
|
|
50
|
+
files: number;
|
|
51
|
+
bytes: number;
|
|
52
|
+
}
|
|
53
|
+
| undefined;
|
|
54
|
+
let failure: Error | undefined;
|
|
55
|
+
worker.once("message", (value) => {
|
|
56
|
+
message = value;
|
|
57
|
+
});
|
|
58
|
+
worker.once("error", (error) => {
|
|
59
|
+
failure = error;
|
|
60
|
+
});
|
|
61
|
+
// Wait for exit so no mutation or Git process survives command settlement.
|
|
62
|
+
worker.once("exit", (code) => {
|
|
63
|
+
try {
|
|
64
|
+
input.onMetrics?.({
|
|
65
|
+
operation: input.operation,
|
|
66
|
+
elapsedMs: performance.now() - started,
|
|
67
|
+
files: message?.files ?? 0,
|
|
68
|
+
bytes: message?.bytes ?? 0,
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
/* Metrics cannot change workflow facts. */
|
|
72
|
+
}
|
|
73
|
+
if (input.signal?.aborted) reject(input.signal.reason);
|
|
74
|
+
else if (failure || code !== 0 || !message)
|
|
75
|
+
reject(new Error("workspace-io-unavailable"));
|
|
76
|
+
else if (!message.ok)
|
|
77
|
+
reject(new Error(message.error ?? "workspace-io-failed"));
|
|
78
|
+
else resolve(message.result as T);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
} finally {
|
|
82
|
+
input.signal?.removeEventListener("abort", abort);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/workspace-store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import {
|
|
4
4
|
chmodSync,
|
|
@@ -14,10 +14,12 @@ import {
|
|
|
14
14
|
writeFileSync,
|
|
15
15
|
} from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
|
-
|
|
17
|
+
import { promisify } from "node:util";
|
|
18
18
|
import type { ArtifactStore } from "./artifact-store.ts";
|
|
19
|
+
import { compareCanonicalStrings } from "./canonical.ts";
|
|
19
20
|
import { isValidRelativePath } from "./contracts.ts";
|
|
20
21
|
import { observeSafePath } from "./safe-path.ts";
|
|
22
|
+
import { runWorkspaceIo, type WorkspaceIoMetrics } from "./workspace-io.ts";
|
|
21
23
|
|
|
22
24
|
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
23
25
|
|
|
@@ -43,6 +45,7 @@ export interface WorkspaceRevision {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export interface CaptureBaselineInput {
|
|
48
|
+
gitTimeoutMs?: number;
|
|
46
49
|
consumerRoot: string;
|
|
47
50
|
approvedUntracked?: string[];
|
|
48
51
|
absent?: string[];
|
|
@@ -80,7 +83,7 @@ function hash(...values: Array<string | Uint8Array>): string {
|
|
|
80
83
|
function stableEntries(entries: WorkspaceEntries): WorkspaceEntries {
|
|
81
84
|
return Object.fromEntries(
|
|
82
85
|
Object.entries(entries)
|
|
83
|
-
.sort(([left], [right]) => left
|
|
86
|
+
.sort(([left], [right]) => compareCanonicalStrings(left, right))
|
|
84
87
|
.map(([relative, entry]) => [relative, { ...entry }]),
|
|
85
88
|
);
|
|
86
89
|
}
|
|
@@ -258,7 +261,19 @@ export class WorkspaceStore {
|
|
|
258
261
|
readonly #revisions: string;
|
|
259
262
|
readonly #artifacts: ArtifactStore;
|
|
260
263
|
|
|
261
|
-
|
|
264
|
+
readonly #checkCancelled: () => void;
|
|
265
|
+
readonly #onMetrics?: (metrics: WorkspaceIoMetrics) => void;
|
|
266
|
+
|
|
267
|
+
constructor(
|
|
268
|
+
root: string,
|
|
269
|
+
artifacts: ArtifactStore,
|
|
270
|
+
options: {
|
|
271
|
+
checkCancelled?: () => void;
|
|
272
|
+
onMetrics?: (metrics: WorkspaceIoMetrics) => void;
|
|
273
|
+
} = {},
|
|
274
|
+
) {
|
|
275
|
+
this.#checkCancelled = options.checkCancelled ?? (() => {});
|
|
276
|
+
this.#onMetrics = options.onMetrics;
|
|
262
277
|
if (!path.isAbsolute(root)) throw new Error("workspace-store-root-invalid");
|
|
263
278
|
this.root = path.resolve(root);
|
|
264
279
|
this.#revisions = path.join(this.root, "revisions");
|
|
@@ -296,8 +311,12 @@ export class WorkspaceStore {
|
|
|
296
311
|
}
|
|
297
312
|
|
|
298
313
|
writeAtomic(target, Buffer.from(`${JSON.stringify(revision)}\n`));
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
const inherited = parentRevisionId
|
|
315
|
+
? this.getRevision(parentRevisionId).entries
|
|
316
|
+
: {};
|
|
317
|
+
for (const [relative, entry] of Object.entries(entries)) {
|
|
318
|
+
if (entry.kind === "file" && !sameEntry(entry, inherited[relative]))
|
|
319
|
+
this.#artifacts.retain(entry.hash);
|
|
301
320
|
}
|
|
302
321
|
return structuredClone(revision);
|
|
303
322
|
}
|
|
@@ -327,12 +346,27 @@ export class WorkspaceStore {
|
|
|
327
346
|
const excluded = [".git", "node_modules", ...(input.excludedPaths ?? [])];
|
|
328
347
|
for (const relative of excluded) requireSafeRelative(relative);
|
|
329
348
|
|
|
349
|
+
return this.#captureBaselineFiles(
|
|
350
|
+
input,
|
|
351
|
+
this.#trackedFiles(input),
|
|
352
|
+
consumerRoot,
|
|
353
|
+
excluded,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#trackedFiles(input: CaptureBaselineInput): string[] {
|
|
358
|
+
const timeout = input.gitTimeoutMs ?? 30_000;
|
|
359
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30_000)
|
|
360
|
+
throw new Error("workspace-git-timeout-invalid");
|
|
361
|
+
const consumerRoot = path.resolve(input.consumerRoot);
|
|
330
362
|
let tracked: string[];
|
|
331
363
|
try {
|
|
332
364
|
tracked = parseNullSeparated(
|
|
333
365
|
execFileSync("git", ["ls-files", "-z", "--cached"], {
|
|
334
366
|
cwd: consumerRoot,
|
|
335
367
|
maxBuffer: 32 * 1024 * 1024,
|
|
368
|
+
timeout,
|
|
369
|
+
killSignal: "SIGKILL",
|
|
336
370
|
}),
|
|
337
371
|
);
|
|
338
372
|
} catch (error) {
|
|
@@ -342,11 +376,21 @@ export class WorkspaceStore {
|
|
|
342
376
|
throw new Error("workspace-git-baseline-unavailable");
|
|
343
377
|
}
|
|
344
378
|
|
|
379
|
+
return tracked;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#captureBaselineFiles(
|
|
383
|
+
input: CaptureBaselineInput,
|
|
384
|
+
tracked: string[],
|
|
385
|
+
consumerRoot: string,
|
|
386
|
+
excluded: string[],
|
|
387
|
+
): WorkspaceRevision {
|
|
345
388
|
const approved = input.approvedUntracked ?? [];
|
|
346
389
|
const absent = input.absent ?? [];
|
|
347
390
|
const selected = [...new Set([...tracked, ...approved])].sort();
|
|
348
391
|
const entries: WorkspaceEntries = {};
|
|
349
392
|
for (const relative of selected) {
|
|
393
|
+
this.#checkCancelled();
|
|
350
394
|
requireSafeRelative(relative);
|
|
351
395
|
if (isExcluded(relative, excluded)) continue;
|
|
352
396
|
const observation = observeSafePath(consumerRoot, relative);
|
|
@@ -393,18 +437,13 @@ export class WorkspaceStore {
|
|
|
393
437
|
const revision = requireRevisionShape(parsed);
|
|
394
438
|
if (
|
|
395
439
|
revision.revisionId !== id ||
|
|
396
|
-
manifestHash(revision.entries) !== revision.manifestHash
|
|
440
|
+
(manifestHash(revision.entries) !== revision.manifestHash &&
|
|
441
|
+
hash("cadence-workspace-manifest", JSON.stringify(revision.entries)) !==
|
|
442
|
+
revision.manifestHash) ||
|
|
397
443
|
revisionId(revision.parentRevisionId, revision.manifestHash) !== id
|
|
398
444
|
) {
|
|
399
445
|
throw new Error("workspace-revision-integrity-invalid");
|
|
400
446
|
}
|
|
401
|
-
for (const entry of Object.values(revision.entries)) {
|
|
402
|
-
if (entry.kind !== "file") continue;
|
|
403
|
-
const bytes = this.#artifacts.read(entry.hash);
|
|
404
|
-
if (bytes.byteLength !== entry.bytes) {
|
|
405
|
-
throw new Error("workspace-revision-integrity-invalid");
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
447
|
return structuredClone(revision);
|
|
409
448
|
}
|
|
410
449
|
|
|
@@ -412,7 +451,7 @@ export class WorkspaceStore {
|
|
|
412
451
|
const parent = this.getRevision(input.parentRevisionId);
|
|
413
452
|
const entries = stableEntries(parent.entries);
|
|
414
453
|
for (const [relative, change] of Object.entries(input.changes).sort(
|
|
415
|
-
([left], [right]) => left
|
|
454
|
+
([left], [right]) => compareCanonicalStrings(left, right),
|
|
416
455
|
)) {
|
|
417
456
|
requireSafeRelative(relative);
|
|
418
457
|
if (
|
|
@@ -498,6 +537,7 @@ export class WorkspaceStore {
|
|
|
498
537
|
const root = path.resolve(destination);
|
|
499
538
|
ensureDirectory(root);
|
|
500
539
|
for (const [relative, entry] of Object.entries(revision.entries)) {
|
|
540
|
+
this.#checkCancelled();
|
|
501
541
|
requireSafeRelative(relative);
|
|
502
542
|
const observation = observeSafePath(root, relative);
|
|
503
543
|
if (entry.kind === "absent") {
|
|
@@ -525,12 +565,78 @@ export class WorkspaceStore {
|
|
|
525
565
|
if (observation.kind !== "absent") {
|
|
526
566
|
throw new Error("workspace-materialize-conflict");
|
|
527
567
|
}
|
|
528
|
-
const bytes = this.#artifacts.
|
|
529
|
-
if (bytes
|
|
568
|
+
const bytes = this.#artifacts.copyVerified(entry.hash, target);
|
|
569
|
+
if (bytes !== entry.bytes)
|
|
530
570
|
throw new Error("workspace-revision-integrity-invalid");
|
|
531
|
-
}
|
|
532
|
-
writeFileSync(target, bytes, { flag: "wx", mode: entry.mode });
|
|
533
571
|
chmodSync(target, entry.mode);
|
|
534
572
|
}
|
|
535
573
|
}
|
|
574
|
+
/** Used by the trusted I/O worker; Git is bounded and cancellable. */
|
|
575
|
+
async captureBaselineWithGit(
|
|
576
|
+
input: CaptureBaselineInput,
|
|
577
|
+
signal?: AbortSignal,
|
|
578
|
+
): Promise<WorkspaceRevision> {
|
|
579
|
+
signal?.throwIfAborted();
|
|
580
|
+
const consumerRoot = path.resolve(input.consumerRoot);
|
|
581
|
+
const stat = lstatSync(consumerRoot, { throwIfNoEntry: false });
|
|
582
|
+
if (!stat?.isDirectory() || stat.isSymbolicLink())
|
|
583
|
+
throw new Error("unsafe-workspace-path");
|
|
584
|
+
const excluded = [".git", "node_modules", ...(input.excludedPaths ?? [])];
|
|
585
|
+
for (const relative of excluded) requireSafeRelative(relative);
|
|
586
|
+
const timeout = input.gitTimeoutMs ?? 30_000;
|
|
587
|
+
if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 30_000)
|
|
588
|
+
throw new Error("workspace-git-timeout-invalid");
|
|
589
|
+
let tracked: string[];
|
|
590
|
+
try {
|
|
591
|
+
const { stdout } = await promisify(execFile)(
|
|
592
|
+
"git",
|
|
593
|
+
["ls-files", "-z", "--cached"],
|
|
594
|
+
{
|
|
595
|
+
cwd: consumerRoot,
|
|
596
|
+
encoding: "buffer",
|
|
597
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
598
|
+
timeout,
|
|
599
|
+
killSignal: "SIGKILL",
|
|
600
|
+
signal,
|
|
601
|
+
},
|
|
602
|
+
);
|
|
603
|
+
tracked = parseNullSeparated(stdout);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
signal?.throwIfAborted();
|
|
606
|
+
if (error instanceof Error && error.message === "unsafe-workspace-path")
|
|
607
|
+
throw error;
|
|
608
|
+
throw new Error("workspace-git-baseline-unavailable");
|
|
609
|
+
}
|
|
610
|
+
this.#checkCancelled();
|
|
611
|
+
return this.#captureBaselineFiles(input, tracked, consumerRoot, excluded);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
captureBaselineAsync(
|
|
615
|
+
input: CaptureBaselineInput,
|
|
616
|
+
signal?: AbortSignal,
|
|
617
|
+
): Promise<WorkspaceRevision> {
|
|
618
|
+
return runWorkspaceIo({
|
|
619
|
+
root: this.root,
|
|
620
|
+
artifactRoot: this.#artifacts.root,
|
|
621
|
+
operation: "captureBaseline",
|
|
622
|
+
args: [input],
|
|
623
|
+
signal,
|
|
624
|
+
onMetrics: this.#onMetrics,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
materializeAsync(
|
|
629
|
+
revisionId: string,
|
|
630
|
+
destination: string,
|
|
631
|
+
signal?: AbortSignal,
|
|
632
|
+
): Promise<void> {
|
|
633
|
+
return runWorkspaceIo({
|
|
634
|
+
root: this.root,
|
|
635
|
+
artifactRoot: this.#artifacts.root,
|
|
636
|
+
operation: "materialize",
|
|
637
|
+
args: [revisionId, destination],
|
|
638
|
+
signal,
|
|
639
|
+
onMetrics: this.#onMetrics,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
536
642
|
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: abel-workflow
|
|
3
|
-
description: Shared contract loaded only after an explicit /abel-init, /abel-design, /abel-implement, or /abel-diagnose invocation; never activate it for ordinary engineering work
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Abel workflow
|
|
7
|
-
|
|
8
|
-
This Skill applies only after the user explicitly invokes `/abel-init`, `/abel-design`, `/abel-implement`, or `/abel-diagnose`.
|
|
9
|
-
Merely finding these files, an OpenSpec change, an AGENTS index entry, or the word “Abel” does not activate a workflow.
|
|
10
|
-
Load this Skill only for the invoked stage and do not carry stage authority into an ordinary engineering request.
|
|
11
|
-
|
|
12
|
-
Stage prompts own their input and procedure.
|
|
13
|
-
This file owns the cross-stage integrity, recovery, verification, and authority rules.
|
|
14
|
-
|
|
15
|
-
## Gates and trusted delivery
|
|
16
|
-
|
|
17
|
-
Gate A approves the complete observable behavior contract.
|
|
18
|
-
Gate B separately approves the complete technical implementation contract.
|
|
19
|
-
Neither Gate grants tool permission, repository permission, identity, or authority outside its recorded scope.
|
|
20
|
-
|
|
21
|
-
Design and Implement may run in fresh contexts.
|
|
22
|
-
Trusted delivery requires:
|
|
23
|
-
|
|
24
|
-
- canonical Gate A and Gate B proofs for the same root/change/schema, each binding its approval revision, canonical contract hash, and owner-private journal record hash;
|
|
25
|
-
- an owner-private finalization fact binding the exact delivery revision, ready-receipt raw hash, both Gate proofs, and canonical plan hash;
|
|
26
|
-
- safe relative artifact paths and exact raw SHA-256 bindings;
|
|
27
|
-
- a canonical `implement-plan.json` with raw hash and canonical hash;
|
|
28
|
-
- OpenSpec strict validity and planning completeness;
|
|
29
|
-
- Requirement → Scenario → Verification → Task traceability exactly once;
|
|
30
|
-
- executable verification closure and complete output/path/dependency/AGENTS contracts.
|
|
31
|
-
|
|
32
|
-
The loader aggregates all safe delivery diagnostics as `delivery-invalid` before a Worker starts.
|
|
33
|
-
Only tracked task checkbox markers for sealed task ids may normalize `[x]`/`[X]` to `[ ]`; every other byte remains hash-bound.
|
|
34
|
-
|
|
35
|
-
A fresh context validates existing approvals and never asks the user to approve unchanged decisions.
|
|
36
|
-
A new receipt revision is needed only when approved authority changes.
|
|
37
|
-
|
|
38
|
-
## Stage separation
|
|
39
|
-
|
|
40
|
-
- **Init** is deterministic local setup/repair.
|
|
41
|
-
It uses no Subagent dispatch, preserves dirty/human content and nested repositories, and is idempotent.
|
|
42
|
-
- **Design** is evidence and delivery compilation only.
|
|
43
|
-
Product code, tests, implementation candidates, and AGENTS files are read-only.
|
|
44
|
-
The parent sees only its previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch` while Design is active.
|
|
45
|
-
Design may use bounded read-only `design-explorer` packets.
|
|
46
|
-
- **Implement** uses only the durable change command surface.
|
|
47
|
-
It executes the sealed plan in private immutable revisions and applies the complete change once.
|
|
48
|
-
- **Diagnose** independently follows reproduce → falsify → failing regression → minimum repair.
|
|
49
|
-
It does not inherit Implement state, retry policy, or approval classification.
|
|
50
|
-
|
|
51
|
-
Design and Diagnose bounded packets use `action: "run"` with their own stage; every Design packet also binds the durable Design `runId` that requested it.
|
|
52
|
-
Implement never uses that packet protocol.
|
|
53
|
-
Cross-stage calls fail closed.
|
|
54
|
-
|
|
55
|
-
## Durable Design control
|
|
56
|
-
|
|
57
|
-
Design uses one closed private `action: "design"` family from its first operation onward: `start`, `status`, `bind-change`, `record-decision`, `approve-gate`, `write-artifact`, `delete-artifact`, `validate-plan-draft`, `compile-plan`, and `finalize-delivery`.
|
|
58
|
-
`start` accepts either the complete transient requirement or an existing change name; `status` uses the returned run id; `bind-change` names a provisional run only after current Gate A approval.
|
|
59
|
-
The control plane normalizes and hashes transient requirement, decision-contract, and Gate-A contract text itself, and Gate B binds a plan compiled after the current Gate A approval and latest substantive decisions without a caller-supplied hash.
|
|
60
|
-
The owner-private SQLite journal retains only normalized evidence identities/hashes, versioned decision hashes and refs, Gate proof facts, canonical compiled-plan identity/bytes, finalization identity, and idempotent operation outcomes.
|
|
61
|
-
It never retains prompts, transcripts, hidden reasoning, credentials, environment values, or raw model output.
|
|
62
|
-
|
|
63
|
-
Accepted evidence is recorded only after structural validation.
|
|
64
|
-
Same packet/result replay is idempotent; conflicting evidence for one packet id fails closed.
|
|
65
|
-
A later behavior decision invalidates both Gates; a later technical decision or plan compilation invalidates Gate B only.
|
|
66
|
-
|
|
67
|
-
The extension snapshots the exact pre-Design non-dispatch tool set; during Design it retains only the previously active `read`, `grep`, `find`, and `ls` tools plus `abel_dispatch`.
|
|
68
|
-
It removes every write-capable or unknown tool and restores the snapshot on finalization, explicit finish, verified stage switch, session start, or shutdown.
|
|
69
|
-
Private artifact mutation accepts bounded UTF-8 only at `.openspec.yaml`, `proposal.md`, `design.md`, `tasks.md`, `plan-draft.json`, and `specs/<safe-segments>/spec.md` relative to the active change root.
|
|
70
|
-
It creates missing directories component-wise without following symlinks, writes atomically, deletes only an allowed regular file, journals only bounded path/hash outcomes, and replays idempotently.
|
|
71
|
-
Code-owned `gate-a.yaml` and `ready.yaml` receipts remain forbidden; `implement-plan.json` remains unreachable except through compilation.
|
|
72
|
-
|
|
73
|
-
All filesystem-mutating Design operations serialize against the durable expiring finalization lease.
|
|
74
|
-
A competing or stale owner cannot inspect, replace, remove, or commit another operation's receipt bytes, and cleanup is fenced by the current operation's raw receipt hash.
|
|
75
|
-
After current Gate A, the parent sends the fixed `plan-draft.json` input only through `write-artifact`.
|
|
76
|
-
Read-only `validate-plan-draft` runs the same compiler checks without installing files or recording a plan revision and returns bounded task/phase/field diagnostics; unchanged invalid requests are not retried blindly.
|
|
77
|
-
`compile-plan` owns canonical plan construction and installation.
|
|
78
|
-
Gate B binds that exact stored canonical plan hash.
|
|
79
|
-
`finalize-delivery` validates current proofs, OpenSpec, artifacts, traceability, and executable closure, writes Gate A and ready receipts atomically with `ready.yaml` last, rereads them, removes the draft, and completes Design.
|
|
80
|
-
|
|
81
|
-
## Canonical ImplementPlan
|
|
82
|
-
|
|
83
|
-
`ImplementPlan` is the only machine source for implementation mechanics.
|
|
84
|
-
It seals:
|
|
85
|
-
|
|
86
|
-
- exact task ids, dependencies, objectives, context, paths, deletes, conflicts, resources, and locks;
|
|
87
|
-
- structured Red, Green, optional Refactor verification plus verification inputs;
|
|
88
|
-
- unique regular-file outputs and their producer task/phase;
|
|
89
|
-
- task-affected and repair verification;
|
|
90
|
-
- target/affected/full-suite baselines and normalized failure identity;
|
|
91
|
-
- change affected/full-suite/post-apply verification;
|
|
92
|
-
- an explicit `artifactCorrection.maxAttempts` of 2-3 total candidate launches per task phase and operation, including the initial launch;
|
|
93
|
-
- bounded repair policy with `pre-existing | introduced | unresolved | environment` attribution;
|
|
94
|
-
- parent-owned Markdown checkbox tracking;
|
|
95
|
-
- approved managed-only AGENTS operations and verification.
|
|
96
|
-
|
|
97
|
-
The compiler, not prose parsing or the caller, canonicalizes and validates the plan.
|
|
98
|
-
A write set grants authority only; it never proves output existence.
|
|
99
|
-
An absent input must resolve to one declared output from a transitive dependency.
|
|
100
|
-
Unsafe paths, symlink components, missing local capability, cycles, conflicts without ordering, unsupported verification, and unbound outputs close readiness before Gate B.
|
|
101
|
-
|
|
102
|
-
Impact closure must cover existing compatibility evidence, not only newly added tests.
|
|
103
|
-
For public UI or API changes it seals the relevant route authorization, page state, API response and contract, public HTML/template/theme behavior, and approved browser E2E checks.
|
|
104
|
-
|
|
105
|
-
Verification supports only shell-free `vitest`, `package-script`, `static-check`, and ordered `steps` contracts.
|
|
106
|
-
Local runners/scripts, exact command bindings, args, classifications, verification inputs, `minTests`, and `noInstall` semantics are sealed.
|
|
107
|
-
Arbitrary shell, `&&`, path escape, and implicit downloads are forbidden.
|
|
108
|
-
|
|
109
|
-
## Durable control
|
|
110
|
-
|
|
111
|
-
Implement accepts only `start`, `status`, `resume`, `rebind`, `cancel`, and `discard` with stage, change, and operation identity where required.
|
|
112
|
-
The internal-development command surface has no caller-supplied protocol version.
|
|
113
|
-
|
|
114
|
-
- `start` is idempotent for one change run.
|
|
115
|
-
- `status` is local and requires no Worker endpoint.
|
|
116
|
-
- `resume` continues from durable facts; with a new approved `deliveryRevision` and `receiptHash`, it revalidates retained work and invalidates only incompatible tasks.
|
|
117
|
-
- `rebind` selects another route already allowed by policy; it does not change the task contract.
|
|
118
|
-
- `cancel` stops the active operation and preserves accepted progress.
|
|
119
|
-
It is budget-neutral.
|
|
120
|
-
- `discard` is the only explicit destructive terminal command.
|
|
121
|
-
During apply it waits for safe recovery before cleanup.
|
|
122
|
-
|
|
123
|
-
Repeated operation ids replay their committed outcomes.
|
|
124
|
-
Private state retains run journals, plan bindings, normalized task evidence, artifacts, revisions, retry classification, and apply recovery facts; it never stores credentials, environment values, raw prompts, hidden reasoning, transcripts, or raw model output.
|
|
125
|
-
Completed/discarded runs clean private change content after safe settlement.
|
|
126
|
-
|
|
127
|
-
## Red-Green-Refactor and attribution
|
|
128
|
-
|
|
129
|
-
Before the first candidate, record target-contract, task-affected, and full-suite baselines.
|
|
130
|
-
Pre-existing failures remain separate and never satisfy Red.
|
|
131
|
-
|
|
132
|
-
1. **Red:** the approved target must fail with its exact witness identity.
|
|
133
|
-
Syntax/setup/no-test/wrong-identity failures are artifact defects.
|
|
134
|
-
2. **Green:** apply the minimum approved candidate in a disposable child revision and require target plus task-affected verification.
|
|
135
|
-
3. **Refactor:** optional in-boundary improvement with behavior and verification unchanged.
|
|
136
|
-
|
|
137
|
-
The parent control plane alone applies accepted candidate bytes to the private cumulative revision, records `phase-verified`/`repair-verified`, advances one task checkbox, verifies outputs, and schedules dependents.
|
|
138
|
-
A Worker submits one complete structured patch within the declared path set; trusted code validates its exact replacements and file operations, generates the unified diff, and seals the candidate.
|
|
139
|
-
One rejected structural submission may be corrected once inside the same disposable child session; a second rejection ends that session without creating an unbounded model loop or accepting partial output.
|
|
140
|
-
|
|
141
|
-
After all tasks, re-run every affected contract, compare the full suite with baseline, apply and verify sealed AGENTS operations, verify output postconditions, then prepare one currentness-checked journaled transaction and post-apply verification.
|
|
142
|
-
The main workspace remains unchanged before final application eligibility.
|
|
143
|
-
|
|
144
|
-
Attribution behavior:
|
|
145
|
-
|
|
146
|
-
- `pre-existing`: baseline evidence; do not blame or speculatively edit.
|
|
147
|
-
- `introduced`: reopen the owning task as `repairable` and perform bounded minimum repair inside its sealed boundary.
|
|
148
|
-
- `unresolved`: pause with normalized identities and retained cumulative state until ownership evidence is available.
|
|
149
|
-
- `environment`: pause before speculative edits and resume after capability restoration.
|
|
150
|
-
|
|
151
|
-
A full-suite-only baseline failure outside affected contracts does not block completion unless the change introduces or worsens it.
|
|
152
|
-
|
|
153
|
-
## Recovery versus approval
|
|
154
|
-
|
|
155
|
-
Artifact defects, wrong Red, stale revisions, transport errors, environment/adapter failures, capacity, conflicts, cancellation, process interruption, baseline failures, and introduced in-boundary repairs are recoverable Implement facts.
|
|
156
|
-
They never select a workflow stage and never erase compatible progress.
|
|
157
|
-
|
|
158
|
-
Automatic attempts are finite.
|
|
159
|
-
Artifact correction uses the sealed `artifactCorrection.maxAttempts` independently for each task phase and operation; only typed artifact rejection consumes its 2-3 total-launch budget.
|
|
160
|
-
Exhaustion pauses with the final safe code, scope, and last committed revision.
|
|
161
|
-
A later `resume` starts a new operation budget while reusing durable baseline and phase facts.
|
|
162
|
-
Oversized output becomes `needs-task-split`; partial patches are never accepted.
|
|
163
|
-
|
|
164
|
-
Use `approval-needed` only when continuing requires new authority: observable behavior, architecture/policy, dependency, undeclared path, conflict/resource permission, verification contract, AGENTS target/impact/content, or another irreversible scope decision.
|
|
165
|
-
Report the exact missing authority.
|
|
166
|
-
After the user approves a new canonical delivery revision, resume the same run and preserve compatible work.
|
|
167
|
-
|
|
168
|
-
An approval-needed status exposes the closed authority category, required Gate set, safe refs, retained run/revision, and `/abel-design --change <change>` as user guidance only.
|
|
169
|
-
It never invokes Design automatically.
|
|
170
|
-
Bare `resume` or `rebind` is not an immediately legal approval continuation; `conditionalCommands` exposes resume only with a newer `deliveryRevision` and matching `receiptHash`.
|
|
171
|
-
Authority classification uses one exhaustive typed code map; an unknown approval code becomes an `approval-code-invalid` integrity pause rather than a guessed Gate.
|
|
172
|
-
A later explicit Implement status may locally discover a newer receipt only when its change, revision/hash, both current Gate proofs, and owner-private finalization fact match; it exposes the exact pair without admitting it or mutating the run.
|
|
173
|
-
|
|
174
|
-
Do not label an approved AGENTS checkpoint, documentation/test edit, endpoint outage, artifact error, stale snapshot, environment problem, baseline failure, or in-boundary repair as approval-needed.
|
|
175
|
-
|
|
176
|
-
## AGENTS indexes
|
|
177
|
-
|
|
178
|
-
Design audits AGENTS read-only.
|
|
179
|
-
Gate B seals `none | update-existing | create-index | remove-index`, exact target, owner task ids, complete managed block, and verification.
|
|
180
|
-
|
|
181
|
-
Implement applies the sealed operation code-first in the private cumulative revision.
|
|
182
|
-
It preserves every byte outside `<!-- ABEL:AGENTS-INDEX:START -->` and `<!-- ABEL:AGENTS-INDEX:END -->`; `remove-index` deletes the file only when no human content remains.
|
|
183
|
-
A Worker never receives an AGENTS write path.
|
|
184
|
-
Runtime/session ids, timestamps, credentials, approval state, and dirty-state ledgers never enter an index.
|
|
185
|
-
|
|
186
|
-
## Parent and Worker authority
|
|
187
|
-
|
|
188
|
-
The parent owns user decisions, Gates, delivery compilation, candidate acceptance, verification classification, cumulative revisions, tracking, AGENTS, final apply, recovery, and truthful status.
|
|
189
|
-
|
|
190
|
-
Evidence/diagnosis/implementation Agents receive only bounded context and scoped tools.
|
|
191
|
-
They cannot approve a Gate, expand a boundary, apply a patch, edit AGENTS, advance a checkbox, choose a user recovery action, or report completion.
|
|
192
|
-
|
|
193
|
-
`dev-browser` is required only by an explicitly approved browser-E2E contract.
|
|
194
|
-
Its absence pauses that verification only and does not block unrelated tasks or stages.
|
|
195
|
-
|
|
196
|
-
## Truthful finish
|
|
197
|
-
|
|
198
|
-
`queued`, `connecting`, `waiting-first-response`, `running`, `validating`, `retrying`, `verifying`, `paused`, `approval-needed`, `applying`, and `recovering` are nonterminal activity states.
|
|
199
|
-
`operation-cancelled` ends only the current operation; `discarded` and `rejected` are non-success terminal run states.
|
|
200
|
-
Tool settlement is not completion.
|
|
201
|
-
Only durable `completed` after cumulative apply and postconditions receives success.
|
|
202
|
-
Successful Design finalization, terminal Implement settlement, Diagnose `finish`, explicit stage finish, and session shutdown deactivate only `abel_dispatch`, drain packets, clear the parent bridge, and clear active stage identity.
|
|
203
|
-
Design status separates `legalOperations` from top-level `packetActions`; `finish` is never a Design operation and is sent only as `{"action":"finish"}`.
|
|
204
|
-
Gate waits and resumable pauses remain active for direct follow-up.
|
|
205
|
-
Never archive, publish, release, stage, or commit implicitly.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createExtensionRuntime,
|
|
3
|
-
type ResourceLoader,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
|
|
6
|
-
/** ResourceLoader that discovers nothing and supplies only the approved prompt. */
|
|
7
|
-
export class EmptyResourceLoader implements ResourceLoader {
|
|
8
|
-
constructor(private readonly systemPrompt?: string) {}
|
|
9
|
-
getExtensions() {
|
|
10
|
-
return { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
11
|
-
}
|
|
12
|
-
getSkills() {
|
|
13
|
-
return { skills: [], diagnostics: [] };
|
|
14
|
-
}
|
|
15
|
-
getPrompts() {
|
|
16
|
-
return { prompts: [], diagnostics: [] };
|
|
17
|
-
}
|
|
18
|
-
getThemes() {
|
|
19
|
-
return { themes: [], diagnostics: [] };
|
|
20
|
-
}
|
|
21
|
-
getAgentsFiles() {
|
|
22
|
-
return { agentsFiles: [] };
|
|
23
|
-
}
|
|
24
|
-
getSystemPrompt() {
|
|
25
|
-
return this.systemPrompt;
|
|
26
|
-
}
|
|
27
|
-
getSystemPromptSource() {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
getAppendSystemPrompt() {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
getAppendSystemPromptSources() {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
extendResources() {}
|
|
37
|
-
async reload() {}
|
|
38
|
-
}
|