pi-daddy 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/README.md +113 -7
- package/dist/catalog.d.ts +9 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +90 -0
- package/dist/catalog.js.map +1 -1
- package/dist/cli.d.ts +31 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +14 -2
- package/dist/delegate.js.map +1 -1
- package/dist/grant-env.d.ts +73 -0
- package/dist/grant-env.d.ts.map +1 -0
- package/dist/grant-env.js +131 -0
- package/dist/grant-env.js.map +1 -0
- package/dist/grant-store.d.ts +68 -0
- package/dist/grant-store.d.ts.map +1 -0
- package/dist/grant-store.js +142 -0
- package/dist/grant-store.js.map +1 -0
- package/dist/init.d.ts +117 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +286 -0
- package/dist/init.js.map +1 -0
- package/dist/skill-packages.d.ts +128 -0
- package/dist/skill-packages.d.ts.map +1 -0
- package/dist/skill-packages.js +245 -0
- package/dist/skill-packages.js.map +1 -0
- package/extensions/grants-command.ts +31 -0
- package/extensions/grants.ts +57 -5
- package/extensions/init-command.ts +132 -0
- package/extensions/session.ts +75 -6
- package/extensions/spawn-summary.ts +214 -0
- package/package.json +12 -1
- package/src/catalog.ts +92 -0
- package/src/cli.ts +266 -0
- package/src/delegate.ts +14 -2
- package/src/grant-env.ts +188 -0
- package/src/grant-store.ts +151 -0
- package/src/init.ts +344 -0
- package/src/skill-packages.ts +282 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grant for a directory, stored **outside** it.
|
|
3
|
+
*
|
|
4
|
+
* `PI_GRANTS_GRANT` is the propagation channel to children and stays exactly as it was — a parent writes
|
|
5
|
+
* it once per session and every child inherits it. What this adds is a second *source* for the root
|
|
6
|
+
* session's own grant, so an operator does not have to `source` a file and restart pi to be governed.
|
|
7
|
+
*
|
|
8
|
+
* **Outside the workspace, and that is the whole design.** A grant is a ceiling; a ceiling a governed child
|
|
9
|
+
* can rewrite is not a ceiling. `<cwd>/.pi/grants.env` is writable by any child holding `tool:write`, so
|
|
10
|
+
* storing the live grant there would let a child widen the *next* session's ceiling — ADR-0014's
|
|
11
|
+
* self-defeating case verbatim, which is why persisted approvals were moved out of the workspace in the
|
|
12
|
+
* first place. This reuses that pattern exactly: `$PI_CODING_AGENT_DIR/grants/<slug>-<hash>.json`, keyed by
|
|
13
|
+
* the directory, unwritable by a narrowed child because a narrowed child holds no write access to `$HOME`.
|
|
14
|
+
*
|
|
15
|
+
* **It does not defend against a child holding `bash`** (ADR-0012). Nothing here does.
|
|
16
|
+
*
|
|
17
|
+
* `.pi/grants.env` is still written by `init` and is still worth committing — it is the *reviewable record*
|
|
18
|
+
* of the decision, diffable in a PR. It is simply no longer the thing the enforcer reads.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from "node:crypto";
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { mkdir, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { basename, dirname, join } from "node:path";
|
|
26
|
+
import { withFileLock, LockTimeoutError } from "./file-lock.js";
|
|
27
|
+
/** `$PI_CODING_AGENT_DIR`, or pi's default. Same resolution as the approval store. */
|
|
28
|
+
function agentDir() {
|
|
29
|
+
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Where this directory's grant lives.
|
|
33
|
+
*
|
|
34
|
+
* Slug plus a 64-bit hash, exactly as `approvalsPath` does it and for the same two reasons: the basename
|
|
35
|
+
* keeps the directory legible to a human reading it, and the hash is what makes it unambiguous, since two
|
|
36
|
+
* checkouts can share a basename.
|
|
37
|
+
*/
|
|
38
|
+
export function grantStorePath(cwd) {
|
|
39
|
+
const slug = (basename(cwd) || "root").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40);
|
|
40
|
+
const hash = createHash("sha256").update(cwd, "utf8").digest("hex").slice(0, 16);
|
|
41
|
+
return join(agentDir(), "grants", `${slug}-${hash}.json`);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse a store file's text into a grant, or null.
|
|
45
|
+
*
|
|
46
|
+
* Split out from the readers so the **one** validation lives in one place: both the sync and async paths
|
|
47
|
+
* must agree, and two parsers is how they come to disagree.
|
|
48
|
+
*
|
|
49
|
+
* Fails closed on every doubt — a malformed file grants nothing rather than something. The `cwd` check is
|
|
50
|
+
* R-27's: a file copied to another machine or another checkout describes a directory that is not this one,
|
|
51
|
+
* and honouring it would let a grant travel somewhere nobody authorised it for.
|
|
52
|
+
*/
|
|
53
|
+
export function parseGrantFile(text, cwd) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(text);
|
|
56
|
+
if (parsed.version !== 1)
|
|
57
|
+
return null;
|
|
58
|
+
if (parsed.cwd !== cwd)
|
|
59
|
+
return null;
|
|
60
|
+
if (!Array.isArray(parsed.grant))
|
|
61
|
+
return null;
|
|
62
|
+
if (!parsed.grant.every((c) => typeof c === "string" && c.length > 0))
|
|
63
|
+
return null;
|
|
64
|
+
return parsed.grant;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read this directory's stored grant, synchronously.
|
|
72
|
+
*
|
|
73
|
+
* **Sync on purpose, and this is the constraint that shapes the feature.** Whether `delegate` is registered
|
|
74
|
+
* at all is decided when the extension factory runs (S-5: a session without `tool:delegate` must not be
|
|
75
|
+
* offered the tool), and that is before any `await` is possible. An async read would resolve after the
|
|
76
|
+
* decision it exists to inform, so the store would silently fail to grant delegation — the exact class of
|
|
77
|
+
* defect R-38 and R-39 were.
|
|
78
|
+
*/
|
|
79
|
+
export function loadGrantSync(cwd) {
|
|
80
|
+
try {
|
|
81
|
+
return parseGrantFile(readFileSync(grantStorePath(cwd), "utf8"), cwd);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Async twin, for callers that already have one. Same parser, so they cannot disagree. */
|
|
88
|
+
export async function loadGrant(cwd) {
|
|
89
|
+
try {
|
|
90
|
+
return parseGrantFile(await readFile(grantStorePath(cwd), "utf8"), cwd);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Write this directory's grant.
|
|
98
|
+
*
|
|
99
|
+
* Locked with the same lock the ledger and the approval store use, for the same reason: two sessions in one
|
|
100
|
+
* directory running `/grants init` concurrently must not interleave. A lock this cannot take yields `busy`
|
|
101
|
+
* and changes nothing — the caller reports it and the operator retries, which is the honest outcome for a
|
|
102
|
+
* write that never looked at the file (R-68).
|
|
103
|
+
*
|
|
104
|
+
* `wx` on the temp file refuses to follow a pre-existing symlink, and `rename` is atomic within a
|
|
105
|
+
* filesystem, so a reader never sees a half-written grant. Both copied from `writeFileSafely`, deliberately
|
|
106
|
+
* — a second, subtly different atomic-write is how the two come to disagree about what "safe" meant.
|
|
107
|
+
*/
|
|
108
|
+
export async function saveGrant(cwd, grant) {
|
|
109
|
+
const path = grantStorePath(cwd);
|
|
110
|
+
const file = { version: 1, cwd, grant: [...grant].sort(), writtenAt: new Date().toISOString() };
|
|
111
|
+
try {
|
|
112
|
+
await mkdir(dirname(path), { recursive: true });
|
|
113
|
+
return await withFileLock(path, "grant store", async () => {
|
|
114
|
+
const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
115
|
+
try {
|
|
116
|
+
await writeFile(temp, `${JSON.stringify(file, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
117
|
+
await rename(temp, path);
|
|
118
|
+
return "saved";
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
await unlink(temp).catch(() => undefined);
|
|
122
|
+
return "failed";
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return error instanceof LockTimeoutError ? "busy" : "failed";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Remove this directory's stored grant. True when a file was there to remove. */
|
|
131
|
+
export async function clearGrant(cwd) {
|
|
132
|
+
const path = grantStorePath(cwd);
|
|
133
|
+
try {
|
|
134
|
+
await readFile(path, "utf8");
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
await rm(path, { force: true }).catch(() => undefined);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=grant-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant-store.js","sourceRoot":"","sources":["../src/grant-store.ts"],"names":[],"mappings":"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"}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pi-daddy init` — scaffold a governed project from the skill packages already installed (B2, P3).
|
|
3
|
+
*
|
|
4
|
+
* Today an operator wanting to govern a package of skills must, per skill: create a directory, copy the
|
|
5
|
+
* body, hand-write frontmatter, choose a capability set with no guidance, and assemble a `PI_GRANTS_GRANT`
|
|
6
|
+
* string by hand. Seven times, for `principal-pi-skills`. This does the mechanical parts.
|
|
7
|
+
*
|
|
8
|
+
* **The line it does not cross, and the reason this module exists at all:** `init` writes files an operator
|
|
9
|
+
* then **reviews, edits and commits**. It never chooses a ceiling. A skill that declares `allowed-tools` is
|
|
10
|
+
* copied *verbatim* — the author's declaration is the ceiling, and re-deriving it here would put a second
|
|
11
|
+
* opinion between the file and the enforcer. A skill that declares none is copied with a **commented**
|
|
12
|
+
* placeholder and stays unspawnable until a human fills it in.
|
|
13
|
+
*
|
|
14
|
+
* **What it DOES choose is the starting grant, which is a stronger act** — ADR-0029, added after a reviewer
|
|
15
|
+
* pointed out that ADR-0028 drew its boundary around the wrong object. The handoff's reason a third party
|
|
16
|
+
* may safely author `allowed-tools` is that *"the operator's `PI_GRANTS_GRANT` still bounds it"*; a
|
|
17
|
+
* generated union gives the bound and the bounded one author, and it is not the operator. So capabilities
|
|
18
|
+
* that can change a machine are emitted **commented** (`./grant-env.ts`).
|
|
19
|
+
*
|
|
20
|
+
* The placeholder is deliberately not a working example. Uncommenting it unedited yields capability ids
|
|
21
|
+
* like `tool:<list` which the catalog refuses as unknown — a loud failure, in the direction rule 8 asks
|
|
22
|
+
* for, rather than a grant nobody decided.
|
|
23
|
+
*/
|
|
24
|
+
import type { Capability } from "./resolve.ts";
|
|
25
|
+
import type { SkillPackage } from "./skill-packages.ts";
|
|
26
|
+
/** Why a discovered skill is not authorised in the generated grant. `null` means it is. */
|
|
27
|
+
/**
|
|
28
|
+
* How many of `from`'s skills actually **declare** a ceiling.
|
|
29
|
+
*
|
|
30
|
+
* Exported because the number is printed to an operator and was wrong: `cli.ts` filtered on
|
|
31
|
+
* `withheld === null`, which is false for all three `WithholdReason`s — so a skill that declares
|
|
32
|
+
* `allowed-tools` perfectly well and merely needs a withheld capability counted as not declaring one.
|
|
33
|
+
* Against `principal-pi-skills` that printed *"7 skill(s), 3 declaring allowed-tools"* while all seven
|
|
34
|
+
* declared. R-28's shape: a diagnostic disagreeing with the thing it describes.
|
|
35
|
+
*
|
|
36
|
+
* **It was invisible until the integration worked.** Before ceilings shipped, none of the seven declared
|
|
37
|
+
* and the line read *"0 declaring"* — correct by coincidence, for the wrong reason. A count that is right
|
|
38
|
+
* only while the interesting case is absent is the kind this project keeps finding.
|
|
39
|
+
*
|
|
40
|
+
* `undeclared` is the only reason that means "did not declare". `pattern` declared something this package
|
|
41
|
+
* refuses to reinterpret, and `needs-withheld` declared something fine that the operator must opt into —
|
|
42
|
+
* both are declarations.
|
|
43
|
+
*/
|
|
44
|
+
export declare function countDeclaring(skills: PlannedSkill[], from: string): number;
|
|
45
|
+
export type WithholdReason = "undeclared" | "pattern" | "needs-withheld";
|
|
46
|
+
export interface PlannedSkill {
|
|
47
|
+
name: string;
|
|
48
|
+
/** Which package it came from, as `name@version`. */
|
|
49
|
+
from: string;
|
|
50
|
+
sourcePath: string;
|
|
51
|
+
targetPath: string;
|
|
52
|
+
/** Exactly what would be written — the file verbatim, or the file plus a commented note. */
|
|
53
|
+
content: string;
|
|
54
|
+
/** The declared ceiling, empty when the declaration is absent or unusable. */
|
|
55
|
+
ceiling: Capability[];
|
|
56
|
+
withheld: WithholdReason | null;
|
|
57
|
+
/** e.g. a sub-tool pattern pi's `--tools` cannot express. Reported, never reinterpreted. */
|
|
58
|
+
notes: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface InitPlan {
|
|
61
|
+
skills: PlannedSkill[];
|
|
62
|
+
/** Definitions two packages both declare. First wins; the loser is named rather than silently dropped. */
|
|
63
|
+
collisions: string[];
|
|
64
|
+
/** The live grant — what `source .pi/grants.env` actually sets. */
|
|
65
|
+
grant: Capability[];
|
|
66
|
+
/** Withheld capability → the definitions that declared it (ADR-0029). Emitted commented. */
|
|
67
|
+
withheldCapabilities: Map<Capability, string[]>;
|
|
68
|
+
grantEnvPath: string;
|
|
69
|
+
grantEnvContent: string;
|
|
70
|
+
/** Capabilities a declared ceiling names that pi 0.84.1 has no tool for — a caution, not a verdict. */
|
|
71
|
+
cautions: string[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The file to write for one skill: verbatim when it declares a usable ceiling, plus a commented note when
|
|
75
|
+
* it does not.
|
|
76
|
+
*
|
|
77
|
+
* Inserted at the END of the frontmatter block, so the rest of the file — including its own key order and
|
|
78
|
+
* its body — is byte-identical to the package's. A `#` line is a YAML comment and this package's
|
|
79
|
+
* frontmatter reader skips it, so an undeclared copy is still *undeclared*: not spawnable until a human
|
|
80
|
+
* edits it, which is the whole point.
|
|
81
|
+
*/
|
|
82
|
+
export declare function withPlaceholder(text: string, declared: boolean, note?: string[]): string;
|
|
83
|
+
/**
|
|
84
|
+
* Decide what `init` would write. Pure: no filesystem, no npm, no decisions taken on the operator's behalf.
|
|
85
|
+
*
|
|
86
|
+
* The grant is **the read-only part** of what the copied skills declare, plus one `agent:` id per definition
|
|
87
|
+
* that can actually run within it, plus `tool:delegate` — without which the session registers no delegation
|
|
88
|
+
* tools at all and the whole file is inert. Everything else is emitted commented, named, and one uncomment
|
|
89
|
+
* away (ADR-0029).
|
|
90
|
+
*/
|
|
91
|
+
export declare function planInit(packages: SkillPackage[], cwd: string): InitPlan;
|
|
92
|
+
export interface InitOutcome {
|
|
93
|
+
written: string[];
|
|
94
|
+
/** Present already, so left alone. `init` never silently overwrites an edited ceiling. */
|
|
95
|
+
kept: string[];
|
|
96
|
+
failed: {
|
|
97
|
+
path: string;
|
|
98
|
+
error: string;
|
|
99
|
+
}[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Apply a plan.
|
|
103
|
+
*
|
|
104
|
+
* **Existing files are kept, not overwritten**, and that default is load-bearing rather than polite: the
|
|
105
|
+
* edit an operator makes to one of these files IS the capability decision, and the second run of a
|
|
106
|
+
* scaffolding command is exactly when it would be destroyed.
|
|
107
|
+
*
|
|
108
|
+
* **`--force` never regenerates `.pi/grants.env`** (R-79). It rewrites the definition copies, which is the
|
|
109
|
+
* documented re-sync path for R-74 — but the grant file is the *reviewed artifact*, and an operator who had
|
|
110
|
+
* deleted `agent:build` and added a ledger path would have had both silently restored to generated defaults
|
|
111
|
+
* by a command whose usage text mentions only `allowed-tools`. Deleting the file is how to regenerate it,
|
|
112
|
+
* and that is not something anyone does by accident.
|
|
113
|
+
*/
|
|
114
|
+
export declare function applyInit(plan: InitPlan, options?: {
|
|
115
|
+
force?: boolean;
|
|
116
|
+
}): Promise<InitOutcome>;
|
|
117
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,2FAA2F;AAC3F;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAEzE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,4FAA4F;IAC5F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,0GAA0G;IAC1G,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,4FAA4F;IAC5F,oBAAoB,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,uGAAuG;IACvG,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAyBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAE,MAAM,EAAgB,GAAG,MAAM,CASrG;AAUD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAuHxE;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0FAA0F;IAC1F,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAkDD;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CASvG"}
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pi-daddy init` — scaffold a governed project from the skill packages already installed (B2, P3).
|
|
3
|
+
*
|
|
4
|
+
* Today an operator wanting to govern a package of skills must, per skill: create a directory, copy the
|
|
5
|
+
* body, hand-write frontmatter, choose a capability set with no guidance, and assemble a `PI_GRANTS_GRANT`
|
|
6
|
+
* string by hand. Seven times, for `principal-pi-skills`. This does the mechanical parts.
|
|
7
|
+
*
|
|
8
|
+
* **The line it does not cross, and the reason this module exists at all:** `init` writes files an operator
|
|
9
|
+
* then **reviews, edits and commits**. It never chooses a ceiling. A skill that declares `allowed-tools` is
|
|
10
|
+
* copied *verbatim* — the author's declaration is the ceiling, and re-deriving it here would put a second
|
|
11
|
+
* opinion between the file and the enforcer. A skill that declares none is copied with a **commented**
|
|
12
|
+
* placeholder and stays unspawnable until a human fills it in.
|
|
13
|
+
*
|
|
14
|
+
* **What it DOES choose is the starting grant, which is a stronger act** — ADR-0029, added after a reviewer
|
|
15
|
+
* pointed out that ADR-0028 drew its boundary around the wrong object. The handoff's reason a third party
|
|
16
|
+
* may safely author `allowed-tools` is that *"the operator's `PI_GRANTS_GRANT` still bounds it"*; a
|
|
17
|
+
* generated union gives the bound and the bounded one author, and it is not the operator. So capabilities
|
|
18
|
+
* that can change a machine are emitted **commented** (`./grant-env.ts`).
|
|
19
|
+
*
|
|
20
|
+
* The placeholder is deliberately not a working example. Uncommenting it unedited yields capability ids
|
|
21
|
+
* like `tool:<list` which the catalog refuses as unknown — a loud failure, in the direction rule 8 asks
|
|
22
|
+
* for, rather than a grant nobody decided.
|
|
23
|
+
*/
|
|
24
|
+
import { mkdir, open, rm, writeFile } from "node:fs/promises";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { agentCapability } from "./capabilities.js";
|
|
27
|
+
import { ceilingForDefinition } from "./definitions.js";
|
|
28
|
+
import { ALWAYS_LIVE, assertGrantIsWritable, isLiveByDefault, renderGrantEnv } from "./grant-env.js";
|
|
29
|
+
import { PI_BUILTIN_TOOLS } from "./pi-tools.js";
|
|
30
|
+
/** Why a discovered skill is not authorised in the generated grant. `null` means it is. */
|
|
31
|
+
/**
|
|
32
|
+
* How many of `from`'s skills actually **declare** a ceiling.
|
|
33
|
+
*
|
|
34
|
+
* Exported because the number is printed to an operator and was wrong: `cli.ts` filtered on
|
|
35
|
+
* `withheld === null`, which is false for all three `WithholdReason`s — so a skill that declares
|
|
36
|
+
* `allowed-tools` perfectly well and merely needs a withheld capability counted as not declaring one.
|
|
37
|
+
* Against `principal-pi-skills` that printed *"7 skill(s), 3 declaring allowed-tools"* while all seven
|
|
38
|
+
* declared. R-28's shape: a diagnostic disagreeing with the thing it describes.
|
|
39
|
+
*
|
|
40
|
+
* **It was invisible until the integration worked.** Before ceilings shipped, none of the seven declared
|
|
41
|
+
* and the line read *"0 declaring"* — correct by coincidence, for the wrong reason. A count that is right
|
|
42
|
+
* only while the interesting case is absent is the kind this project keeps finding.
|
|
43
|
+
*
|
|
44
|
+
* `undeclared` is the only reason that means "did not declare". `pattern` declared something this package
|
|
45
|
+
* refuses to reinterpret, and `needs-withheld` declared something fine that the operator must opt into —
|
|
46
|
+
* both are declarations.
|
|
47
|
+
*/
|
|
48
|
+
export function countDeclaring(skills, from) {
|
|
49
|
+
return skills.filter((s) => s.from === from && s.withheld !== "undeclared").length;
|
|
50
|
+
}
|
|
51
|
+
const PLACEHOLDER = [
|
|
52
|
+
"# pi-daddy: this skill declares no `allowed-tools`, so it CANNOT be spawned as a governed sub-agent —",
|
|
53
|
+
"# an undeclared capability set is treated as NONE, never as everything. Decide what it needs, then",
|
|
54
|
+
"# uncomment and complete the line below. pi-daddy does not choose this for you: the capability set is",
|
|
55
|
+
"# the thing you are meant to review and commit.",
|
|
56
|
+
"# pi 0.84.1's tools: " + PI_BUILTIN_TOOLS.join(", ") + ".",
|
|
57
|
+
"# allowed-tools: <list the tools this skill needs, e.g. Read, Grep>",
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* A copied file that is unspawnable for a reason an operator cannot see by reading it.
|
|
61
|
+
*
|
|
62
|
+
* The undeclared case got four explanatory lines from the start; a pattern-carrying one got nothing, so
|
|
63
|
+
* opening `.pi/skills/git-ops/SKILL.md` to find out why it will not spawn showed a perfectly ordinary file.
|
|
64
|
+
* Same argument, opposite treatment — now the same treatment.
|
|
65
|
+
*/
|
|
66
|
+
const patternNote = (patterns) => [
|
|
67
|
+
"# pi-daddy: this skill CANNOT be spawned as a governed sub-agent. Its `allowed-tools` restricts a tool",
|
|
68
|
+
`# with a pattern (${patterns.join(", ")}), and pi's --tools matches whole tool names only — granting the`,
|
|
69
|
+
"# bare tool would widen the declaration and dropping it would silently narrow, so neither is done.",
|
|
70
|
+
"# Replace the pattern with whole tool names to make it spawnable.",
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* The file to write for one skill: verbatim when it declares a usable ceiling, plus a commented note when
|
|
74
|
+
* it does not.
|
|
75
|
+
*
|
|
76
|
+
* Inserted at the END of the frontmatter block, so the rest of the file — including its own key order and
|
|
77
|
+
* its body — is byte-identical to the package's. A `#` line is a YAML comment and this package's
|
|
78
|
+
* frontmatter reader skips it, so an undeclared copy is still *undeclared*: not spawnable until a human
|
|
79
|
+
* edits it, which is the whole point.
|
|
80
|
+
*/
|
|
81
|
+
export function withPlaceholder(text, declared, note = PLACEHOLDER) {
|
|
82
|
+
if (declared)
|
|
83
|
+
return text;
|
|
84
|
+
const match = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(text);
|
|
85
|
+
// No frontmatter at all means `parseSkillDefinition` returned null and this skill was never discovered;
|
|
86
|
+
// inventing one here would invent a file shape. Left exactly as it is.
|
|
87
|
+
if (!match)
|
|
88
|
+
return text;
|
|
89
|
+
const [full, opening, body, close] = match;
|
|
90
|
+
const eol = close.startsWith("\r\n") ? "\r\n" : "\n";
|
|
91
|
+
return opening + body + eol + note.join(eol) + close + text.slice(full.length);
|
|
92
|
+
}
|
|
93
|
+
/** `tool:` ids pi 0.84.1 has no tool for. `delegate` is this package's own, hence the exemption. */
|
|
94
|
+
function unknownToolIds(capabilities) {
|
|
95
|
+
const builtins = new Set(PI_BUILTIN_TOOLS);
|
|
96
|
+
return capabilities.filter((c) => c.startsWith("tool:") && c !== ALWAYS_LIVE && !builtins.has(c.slice("tool:".length)));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Decide what `init` would write. Pure: no filesystem, no npm, no decisions taken on the operator's behalf.
|
|
100
|
+
*
|
|
101
|
+
* The grant is **the read-only part** of what the copied skills declare, plus one `agent:` id per definition
|
|
102
|
+
* that can actually run within it, plus `tool:delegate` — without which the session registers no delegation
|
|
103
|
+
* tools at all and the whole file is inert. Everything else is emitted commented, named, and one uncomment
|
|
104
|
+
* away (ADR-0029).
|
|
105
|
+
*/
|
|
106
|
+
export function planInit(packages, cwd) {
|
|
107
|
+
const skills = [];
|
|
108
|
+
const collisions = [];
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
for (const pkg of packages) {
|
|
111
|
+
for (const skill of pkg.skills) {
|
|
112
|
+
const name = skill.definition.name;
|
|
113
|
+
if (seen.has(name)) {
|
|
114
|
+
collisions.push(`${name} (also in ${pkg.name}@${pkg.version}, not written)`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
seen.add(name);
|
|
118
|
+
const ceiling = ceilingForDefinition(skill.definition);
|
|
119
|
+
const notes = [];
|
|
120
|
+
let withheld = null;
|
|
121
|
+
let note = PLACEHOLDER;
|
|
122
|
+
if (ceiling.undeclared)
|
|
123
|
+
withheld = "undeclared";
|
|
124
|
+
else if (ceiling.patterns.length > 0) {
|
|
125
|
+
withheld = "pattern";
|
|
126
|
+
note = patternNote(ceiling.patterns);
|
|
127
|
+
notes.push(`declares ${ceiling.patterns.join(", ")} — pi's --tools matches whole tool names only, so a ` +
|
|
128
|
+
`sub-tool pattern is refused rather than reinterpreted (granting the bare tool would widen it)`);
|
|
129
|
+
}
|
|
130
|
+
skills.push({
|
|
131
|
+
name,
|
|
132
|
+
from: `${pkg.name}@${pkg.version}`,
|
|
133
|
+
sourcePath: skill.path,
|
|
134
|
+
targetPath: join(cwd, ".pi", "skills", name, "SKILL.md"),
|
|
135
|
+
content: withPlaceholder(skill.text, withheld === null, note),
|
|
136
|
+
ceiling: ceiling.capabilities,
|
|
137
|
+
withheld,
|
|
138
|
+
notes,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ADR-0029: split what the declared ceilings ask for into what `init` grants live and what it comments.
|
|
143
|
+
const declared = skills.filter((s) => s.withheld === null);
|
|
144
|
+
const withheldCapabilities = new Map();
|
|
145
|
+
for (const skill of declared) {
|
|
146
|
+
for (const capability of skill.ceiling) {
|
|
147
|
+
if (isLiveByDefault(capability))
|
|
148
|
+
continue;
|
|
149
|
+
withheldCapabilities.set(capability, [...(withheldCapabilities.get(capability) ?? []), skill.name]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// A definition needing a withheld capability does not get a live `agent:` id either: authorising it to run
|
|
153
|
+
// and then refusing it at spawn time is a worse answer than not authorising it, and it would put an
|
|
154
|
+
// `agent:` id in the grant whose definition cannot work — the shape ADR-0028 rule 3 already refuses.
|
|
155
|
+
for (const skill of declared) {
|
|
156
|
+
if (skill.ceiling.some((c) => !isLiveByDefault(c)))
|
|
157
|
+
skill.withheld = "needs-withheld";
|
|
158
|
+
}
|
|
159
|
+
const authorised = skills.filter((s) => s.withheld === null);
|
|
160
|
+
const written = new Set(authorised.map((s) => s.name));
|
|
161
|
+
// `agent:<other>` in a ceiling is legitimate — it is how a delegator learns which definitions IT may
|
|
162
|
+
// spawn — but a name `init` did not write here would authorise a file from another skill root that the
|
|
163
|
+
// operator is not reviewing, including `~/.pi/agent/skills`, which other tools install into. Reported,
|
|
164
|
+
// never granted: the same objection ADR-0028 rule 3 makes to authorising an undeclared skill.
|
|
165
|
+
const crossReferences = [];
|
|
166
|
+
const live = new Set([ALWAYS_LIVE]);
|
|
167
|
+
for (const skill of authorised) {
|
|
168
|
+
live.add(agentCapability(skill.name));
|
|
169
|
+
for (const capability of skill.ceiling) {
|
|
170
|
+
if (capability.startsWith("agent:") && !written.has(capability.slice("agent:".length))) {
|
|
171
|
+
crossReferences.push({ from: skill.name, capability });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
live.add(capability);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const grant = [...live].sort();
|
|
178
|
+
const cautions = authorised.flatMap((s) => unknownToolIds(s.ceiling).map((c) => `${s.name} declares ${c}, which pi 0.84.1 has no tool for — unless an extension provides it, ` +
|
|
179
|
+
`spawning ${s.name} is refused as an unknown capability`));
|
|
180
|
+
// R-78's structural backstop. Throws rather than rendering something a shell could read as more than a
|
|
181
|
+
// value, so a gap in the per-entry whitelist costs a refusal instead of an injection.
|
|
182
|
+
assertGrantIsWritable(grant);
|
|
183
|
+
const describe = {
|
|
184
|
+
undeclared: (s) => "declares no `allowed-tools` — fill it in, then add `agent:" + s.name + "` below",
|
|
185
|
+
pattern: (s) => s.notes.join("; "),
|
|
186
|
+
"needs-withheld": (s) => `needs ${s.ceiling.filter((c) => !isLiveByDefault(c)).join(", ")}, withheld by default — see below`,
|
|
187
|
+
};
|
|
188
|
+
const grantEnvSkills = skills.map((s) => ({
|
|
189
|
+
name: s.name,
|
|
190
|
+
ceiling: s.ceiling,
|
|
191
|
+
...(s.withheld ? { unspawnable: describe[s.withheld](s) } : {}),
|
|
192
|
+
}));
|
|
193
|
+
return {
|
|
194
|
+
skills,
|
|
195
|
+
collisions,
|
|
196
|
+
grant,
|
|
197
|
+
withheldCapabilities,
|
|
198
|
+
grantEnvPath: join(cwd, ".pi", "grants.env"),
|
|
199
|
+
grantEnvContent: renderGrantEnv({
|
|
200
|
+
skills: grantEnvSkills,
|
|
201
|
+
live: grant,
|
|
202
|
+
withheld: withheldCapabilities,
|
|
203
|
+
withheldDefinitions: skills.filter((s) => s.withheld === "needs-withheld").map((s) => s.name),
|
|
204
|
+
crossReferences,
|
|
205
|
+
cautions,
|
|
206
|
+
}),
|
|
207
|
+
cautions,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create a file only if nothing is there, and **never through a symlink**.
|
|
212
|
+
*
|
|
213
|
+
* Both properties come from `O_CREAT|O_EXCL` (`flag: "wx"`), and both were defects in the first version
|
|
214
|
+
* (R-79). It probed for existence with `readFile` and then called `writeFile`:
|
|
215
|
+
*
|
|
216
|
+
* - `readFile` conflates *unreadable* with *absent*, so an operator's `SKILL.md` with restrictive
|
|
217
|
+
* permissions was reported `wrote` and their narrowed `allowed-tools: Read` was replaced by the
|
|
218
|
+
* package's wider one — **without `--force`**, falsifying this package's own documented "Kept" rule.
|
|
219
|
+
* A FIFO at the target path hung the probe forever, with no timeout anywhere in the path.
|
|
220
|
+
* - `writeFile` follows symlinks, so a dangling symlink at a target path created the file at the link's
|
|
221
|
+
* destination, outside the project, while reporting an in-project path. That is **B-I6**, which
|
|
222
|
+
* `approval-store.ts` already fixed for the approval store under ADR-0014 — a new writer in the same
|
|
223
|
+
* package reintroducing a defect the package documents as closed, and whose comment says in so many
|
|
224
|
+
* words *"never through a symlink"*.
|
|
225
|
+
*
|
|
226
|
+
* `wx` fails with `EEXIST` on anything at the path, including a dangling symlink, so there is no probe, no
|
|
227
|
+
* race between the probe and the write, and no way to write through a link.
|
|
228
|
+
*/
|
|
229
|
+
async function createUnlessPresent(path, content, outcome) {
|
|
230
|
+
try {
|
|
231
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
232
|
+
const handle = await open(path, "wx");
|
|
233
|
+
try {
|
|
234
|
+
await handle.writeFile(content, "utf8");
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await handle.close();
|
|
238
|
+
}
|
|
239
|
+
outcome.written.push(path);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error.code === "EEXIST")
|
|
243
|
+
outcome.kept.push(path);
|
|
244
|
+
else
|
|
245
|
+
outcome.failed.push({ path, error: error instanceof Error ? error.message : String(error) });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Replace a file, unlinking first so a symlink is REPLACED rather than written through. */
|
|
249
|
+
async function replace(path, content, outcome) {
|
|
250
|
+
try {
|
|
251
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
252
|
+
// `rm` unlinks the LINK, never its target — which is exactly the semantics `--force` should have.
|
|
253
|
+
await rm(path, { force: true });
|
|
254
|
+
await writeFile(path, content, { encoding: "utf8", flag: "wx" });
|
|
255
|
+
outcome.written.push(path);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
outcome.failed.push({ path, error: error instanceof Error ? error.message : String(error) });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Apply a plan.
|
|
263
|
+
*
|
|
264
|
+
* **Existing files are kept, not overwritten**, and that default is load-bearing rather than polite: the
|
|
265
|
+
* edit an operator makes to one of these files IS the capability decision, and the second run of a
|
|
266
|
+
* scaffolding command is exactly when it would be destroyed.
|
|
267
|
+
*
|
|
268
|
+
* **`--force` never regenerates `.pi/grants.env`** (R-79). It rewrites the definition copies, which is the
|
|
269
|
+
* documented re-sync path for R-74 — but the grant file is the *reviewed artifact*, and an operator who had
|
|
270
|
+
* deleted `agent:build` and added a ledger path would have had both silently restored to generated defaults
|
|
271
|
+
* by a command whose usage text mentions only `allowed-tools`. Deleting the file is how to regenerate it,
|
|
272
|
+
* and that is not something anyone does by accident.
|
|
273
|
+
*/
|
|
274
|
+
export async function applyInit(plan, options = {}) {
|
|
275
|
+
const outcome = { written: [], kept: [], failed: [] };
|
|
276
|
+
const force = options.force === true;
|
|
277
|
+
for (const skill of plan.skills) {
|
|
278
|
+
if (force)
|
|
279
|
+
await replace(skill.targetPath, skill.content, outcome);
|
|
280
|
+
else
|
|
281
|
+
await createUnlessPresent(skill.targetPath, skill.content, outcome);
|
|
282
|
+
}
|
|
283
|
+
await createUnlessPresent(plan.grantEnvPath, plan.grantEnvContent, outcome);
|
|
284
|
+
return outcome;
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=init.js.map
|
package/dist/init.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
|