phronesis 1.0.0 → 1.1.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/package.json +1 -1
- package/src/act.js +62 -1
- package/src/adapter.js +366 -61
- package/src/cli.js +527 -32
- package/src/compile.js +141 -10
- package/src/doctor.js +63 -2
- package/src/hook.js +312 -0
- package/src/hooks-refresh.js +1363 -0
- package/src/hooks.js +48 -33
- package/src/init.js +37 -4
- package/src/launcher.js +2105 -0
- package/src/layout.js +51 -7
- package/src/profile.js +169 -0
- package/src/prompts.js +88 -0
- package/src/skills-bump.js +88 -20
- package/src/skills.js +103 -10
- package/templates/codex/INDEX.md +4 -2
- package/templates/codex/seed/ai-practice/the-workspace-is-part-of-the-thinking.md +96 -0
- package/templates/codex-surface/README.md +15 -16
- package/templates/codex-surface/hooks/_resolve.sh +25 -24
- package/templates/codex-surface/hooks/recall-guard.sh +5 -30
- package/templates/codex-surface/hooks/session-start.sh +5 -18
- package/templates/codex-surface/hooks/session-sweep-precompact.sh +5 -20
- package/templates/codex-surface/hooks/session-sweep-subagent.sh +5 -23
- package/templates/codex-surface/hooks/session-sweep.sh +5 -19
- package/templates/launcher/phronesis +95 -0
- package/templates/phronesis-hooks/compile-active-context.sh +12 -38
- package/templates/phronesis-hooks/recall-guard.sh +12 -103
- package/templates/phronesis-hooks/session-start.sh +10 -88
- package/templates/phronesis-hooks/session-sweep.sh +16 -139
- package/templates/phronesis-hooks/skill-lifecycle.sh +12 -37
- package/templates/skills/lint/SKILL.md +7 -2
- package/templates/skills/onboard/SKILL.md +47 -9
- package/templates/skills/onboard/evals/rubric.md +5 -3
- package/templates/skills/prd-draft/SKILL.md +34 -26
- package/templates/skills/prd-draft/evals/rubric.md +1 -1
- package/vendor/core/src/action-registry.js +6 -3
- package/vendor/core/src/actions.js +120 -16
- package/vendor/core/src/index.js +3 -0
- package/vendor/core/src/lint.js +22 -5
- package/vendor/core/src/skill-lifecycle.js +67 -7
package/src/skills.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// in the act pipeline), but it RESOLVES against it: via_action_types must name
|
|
8
8
|
// registered action types, so an invalid action layer blocks skill resolution too.
|
|
9
9
|
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import { readdir, readFile } from 'node:fs/promises';
|
|
10
|
+
import { basename, join } from 'node:path';
|
|
11
|
+
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
14
14
|
import { pathToFileURL } from 'node:url';
|
|
@@ -152,15 +152,18 @@ export async function runSkillsStatus({ dir, json = false } = {}) {
|
|
|
152
152
|
// that repo's, and a packaged CLI without harness/ still loads (the import is lazy).
|
|
153
153
|
|
|
154
154
|
function sha256(value) {
|
|
155
|
-
|
|
155
|
+
const hash = createHash('sha256');
|
|
156
|
+
hash.update(Buffer.isBuffer(value) ? value : String(value), Buffer.isBuffer(value) ? undefined : 'utf8');
|
|
157
|
+
return hash.digest('hex');
|
|
156
158
|
}
|
|
157
159
|
|
|
158
160
|
// Resolve the skill's golden corpus the same way run_skill_goldens.py does:
|
|
159
161
|
// --goldens-dir → $PHRONESIS_INSTALL/corpus/skill-goldens/<skill> (goldens are real
|
|
160
162
|
// operator material; they live in the install, never the repo — change 0023). Builds the
|
|
161
|
-
// name-free corpus identity (sorted id:hash + manifest hash) and reads
|
|
162
|
-
//
|
|
163
|
-
//
|
|
163
|
+
// name-free corpus identity (sorted id:hash + eval-sidecar hash + manifest hash) and reads
|
|
164
|
+
// signedness from either a directory marker or every admitted golden's operator-signature
|
|
165
|
+
// fields. The sidecar carries agent-owned eval mechanics so adding coverage/applicability
|
|
166
|
+
// does not mutate the bytes the operator already signed.
|
|
164
167
|
export async function loadCorpusFromInstall({ skill, goldensDir }) {
|
|
165
168
|
const dir = goldensDir
|
|
166
169
|
|| (process.env.PHRONESIS_INSTALL
|
|
@@ -169,9 +172,16 @@ export async function loadCorpusFromInstall({ skill, goldensDir }) {
|
|
|
169
172
|
if (!dir || !existsSync(dir)) {
|
|
170
173
|
return { goldens: [], signed: false, identity: '', source: dir || '(no $PHRONESIS_INSTALL set)' };
|
|
171
174
|
}
|
|
172
|
-
|
|
175
|
+
// Only primary golden records enter the corpus. Named sidecars such as
|
|
176
|
+
// golden-001.eval.json / .approvals.json are mechanics, not extra goldens.
|
|
177
|
+
const files = (await readdir(dir)).filter((f) => /^golden-[a-z0-9-]+\.json$/i.test(f)).sort();
|
|
173
178
|
const goldens = [];
|
|
174
179
|
const idParts = [];
|
|
180
|
+
const evalSidecarKeys = new Set([
|
|
181
|
+
'coverage_expectations',
|
|
182
|
+
'rubric_criteria_exercised',
|
|
183
|
+
'scenario_preamble',
|
|
184
|
+
]);
|
|
175
185
|
for (const f of files) {
|
|
176
186
|
const p = join(dir, f);
|
|
177
187
|
const raw = await readFile(p, 'utf8');
|
|
@@ -195,8 +205,40 @@ export async function loadCorpusFromInstall({ skill, goldensDir }) {
|
|
|
195
205
|
);
|
|
196
206
|
}
|
|
197
207
|
const id = d.id || f.replace(/\.json$/, '');
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
const evalPath = p.replace(/\.json$/, '.eval.json');
|
|
209
|
+
let evalSidecar = {};
|
|
210
|
+
let evalRaw = '';
|
|
211
|
+
if (existsSync(evalPath)) {
|
|
212
|
+
evalRaw = await readFile(evalPath, 'utf8');
|
|
213
|
+
try {
|
|
214
|
+
evalSidecar = JSON.parse(evalRaw);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`corpus eval sidecar ${basename(evalPath)} is not valid JSON (${err.message}) — ` +
|
|
218
|
+
'fix it; the bump gate fails closed rather than run under partial mechanics.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (!evalSidecar || typeof evalSidecar !== 'object' || Array.isArray(evalSidecar)) {
|
|
222
|
+
throw new Error(`corpus eval sidecar ${basename(evalPath)} must be a JSON object`);
|
|
223
|
+
}
|
|
224
|
+
const unknown = Object.keys(evalSidecar).filter((key) => !evalSidecarKeys.has(key));
|
|
225
|
+
if (unknown.length) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`corpus eval sidecar ${basename(evalPath)} has unknown key(s): ${unknown.join(', ')}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
'scenario_preamble' in evalSidecar
|
|
232
|
+
&& (typeof evalSidecar.scenario_preamble !== 'string' || evalSidecar.scenario_preamble.trim() === '')
|
|
233
|
+
) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`corpus eval sidecar ${basename(evalPath)} scenario_preamble must be a non-empty string`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
goldens.push({ ...d, ...evalSidecar, id, path: p });
|
|
240
|
+
const mechanics = await hashGoldenMechanics(p, evalRaw);
|
|
241
|
+
idParts.push(`${id}:${sha256(raw)}:mechanics:${mechanics}`);
|
|
200
242
|
}
|
|
201
243
|
let manifest = '';
|
|
202
244
|
for (const mp of [join(dir, 'MANIFEST.md'), join(dir, '..', 'MANIFEST.md')]) {
|
|
@@ -206,7 +248,9 @@ export async function loadCorpusFromInstall({ skill, goldensDir }) {
|
|
|
206
248
|
}
|
|
207
249
|
}
|
|
208
250
|
const identity = `${idParts.sort().join('\n')}\n--manifest--\n${sha256(manifest)}`;
|
|
209
|
-
|
|
251
|
+
const individuallySigned =
|
|
252
|
+
goldens.length > 0 && goldens.every((golden) => golden.signed === true && golden.status === 'operator-signed');
|
|
253
|
+
let signed = individuallySigned;
|
|
210
254
|
const signingPath = join(dir, 'signing.json');
|
|
211
255
|
if (existsSync(signingPath)) {
|
|
212
256
|
try {
|
|
@@ -220,6 +264,53 @@ export async function loadCorpusFromInstall({ skill, goldensDir }) {
|
|
|
220
264
|
return { goldens, signed, identity, source: dir };
|
|
221
265
|
}
|
|
222
266
|
|
|
267
|
+
async function hashGoldenMechanics(goldenPath, evalRaw) {
|
|
268
|
+
const base = goldenPath.replace(/\.json$/i, '');
|
|
269
|
+
const entries = [`eval.json:${sha256(evalRaw)}`];
|
|
270
|
+
for (const [label, path] of [
|
|
271
|
+
['approvals.json', `${base}.approvals.json`],
|
|
272
|
+
['workspace', `${base}.workspace`],
|
|
273
|
+
['retrieval', `${base}.retrieval`],
|
|
274
|
+
]) {
|
|
275
|
+
entries.push(`${label}:${await hashMechanicsPath(path, label)}`);
|
|
276
|
+
}
|
|
277
|
+
return sha256(entries.join('\n'));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function hashMechanicsPath(path, label) {
|
|
281
|
+
if (!existsSync(path)) return 'absent';
|
|
282
|
+
const stat = await lstat(path);
|
|
283
|
+
if (stat.isSymbolicLink()) {
|
|
284
|
+
throw new Error(`corpus mechanics ${label} must not contain symlinks: ${path}`);
|
|
285
|
+
}
|
|
286
|
+
if (stat.isFile()) return `file:${sha256(await readFile(path))}`;
|
|
287
|
+
if (!stat.isDirectory()) {
|
|
288
|
+
throw new Error(`corpus mechanics ${label} must be a file or directory: ${path}`);
|
|
289
|
+
}
|
|
290
|
+
const out = [];
|
|
291
|
+
async function walk(dir, rel = '') {
|
|
292
|
+
const children = await readdir(dir, { withFileTypes: true });
|
|
293
|
+
for (const child of children.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
294
|
+
const childRel = rel ? `${rel}/${child.name}` : child.name;
|
|
295
|
+
const childPath = join(dir, child.name);
|
|
296
|
+
const childStat = await lstat(childPath);
|
|
297
|
+
if (childStat.isSymbolicLink()) {
|
|
298
|
+
throw new Error(`corpus mechanics ${label} must not contain symlinks: ${childPath}`);
|
|
299
|
+
}
|
|
300
|
+
if (childStat.isDirectory()) {
|
|
301
|
+
out.push(`dir:${childRel}`);
|
|
302
|
+
await walk(childPath, childRel);
|
|
303
|
+
} else if (childStat.isFile()) {
|
|
304
|
+
out.push(`file:${childRel}:${sha256(await readFile(childPath))}`);
|
|
305
|
+
} else {
|
|
306
|
+
throw new Error(`corpus mechanics ${label} contains an unsupported entry: ${childPath}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
await walk(path);
|
|
311
|
+
return `dir:${sha256(out.join('\n'))}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
223
314
|
export async function runSkillsBump({
|
|
224
315
|
skill,
|
|
225
316
|
to,
|
|
@@ -227,6 +318,7 @@ export async function runSkillsBump({
|
|
|
227
318
|
k = DEFAULT_K,
|
|
228
319
|
acceptRegression = false,
|
|
229
320
|
rebaseline = false,
|
|
321
|
+
verifyCurrent = false,
|
|
230
322
|
shapeChange = false,
|
|
231
323
|
qualityFloor,
|
|
232
324
|
coverageFloor,
|
|
@@ -295,6 +387,7 @@ export async function runSkillsBump({
|
|
|
295
387
|
repoRoot: root,
|
|
296
388
|
k,
|
|
297
389
|
rebaseline,
|
|
390
|
+
verifyCurrent,
|
|
298
391
|
acceptRegression,
|
|
299
392
|
shapeChange,
|
|
300
393
|
qualityFloor,
|
package/templates/codex/INDEX.md
CHANGED
|
@@ -7,8 +7,9 @@ Format: `- [Title](path) — one-line hook`.
|
|
|
7
7
|
## seed/ — immutable seeded principles
|
|
8
8
|
|
|
9
9
|
_Shipped, external starter principles — not your earned codex. Quarantined out of retrieval_
|
|
10
|
-
_until you adopt one
|
|
11
|
-
_
|
|
10
|
+
_until you adopt one, with the full command:_
|
|
11
|
+
_`phronesis act promote_to_codex --json '{"from_seed":"<collection>/<slug>","rationale":"..."}'`_
|
|
12
|
+
_(`act` takes semantic inputs only inside `--json`)._
|
|
12
13
|
_Each carries a `## Interview Prompt` — a question to ask about your own world._
|
|
13
14
|
|
|
14
15
|
### pm/ — product-management frameworks
|
|
@@ -62,6 +63,7 @@ _Each carries a `## Interview Prompt` — a question to ask about your own world
|
|
|
62
63
|
- [The Eval Is the Spec](seed/ai-practice/the-eval-is-the-spec.md) — For a probabilistic product, the specification is the eval set. You define behavior by examples and graders, not prose — because…
|
|
63
64
|
- [The Gold Dust Is What Leaves No Trace](seed/ai-practice/the-gold-dust-leaves-no-trace.md) — The codifiable playbook is already being extracted from recorded traces; the gold dust is the situated judgment read live off an…
|
|
64
65
|
- [The Model Is Not the Moat](seed/ai-practice/the-model-is-not-the-moat.md) — The model is not the moat; the system around it is. When raw capability commoditizes, defensibility lives in proprietary data,…
|
|
66
|
+
- [The Workspace Is Part of the Thinking](seed/ai-practice/the-workspace-is-part-of-the-thinking.md) — A store that is always there, easy to reach, and full of what you ratified is not a record of your thinking. It is part of it.
|
|
65
67
|
- [When Generation Is Cheap, Judgment Is the Work](seed/ai-practice/when-generation-is-cheap-judgment-is-the-work.md) — When generation becomes cheap, the bottleneck moves from making to judging. The scarce skill is knowing which output is actually…
|
|
66
68
|
|
|
67
69
|
## personal/ — operator-earned and adopted
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: codex-principle-seed
|
|
3
|
+
name: the-workspace-is-part-of-the-thinking
|
|
4
|
+
title: "The Workspace Is Part of the Thinking"
|
|
5
|
+
created: 2026-07-12
|
|
6
|
+
exposure: shareable
|
|
7
|
+
stance: adopted
|
|
8
|
+
provenance:
|
|
9
|
+
authored_by: seed
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
> **A store that is always there, easy to reach, and full of what you yourself
|
|
13
|
+
> ratified is not a record of your thinking. It is part of it. Build to that
|
|
14
|
+
> standard, and spend the effort where the tradition put it: not on capturing
|
|
15
|
+
> more, on compounding what was caught.**
|
|
16
|
+
|
|
17
|
+
In 1998 two philosophers asked where the mind stops and the rest of the world
|
|
18
|
+
begins. Their answer was not at the skull. Their test case was a man who
|
|
19
|
+
compensates for failing memory with a notebook. He writes down what he learns
|
|
20
|
+
and looks it up when he needs it, and the notebook is so reliably present that
|
|
21
|
+
he believed what was in it before he opened it. Their claim is that when
|
|
22
|
+
something outside the mind does the work memory would do, it becomes part of the
|
|
23
|
+
thinking, not merely an aid to it. The claim is contested philosophy, not
|
|
24
|
+
settled science, and what has survived more than a quarter-century of
|
|
25
|
+
professional criticism is its tempered form: the store does not mimic the
|
|
26
|
+
brain, it complements it, and the pair outthinks the brain alone. Treat it here
|
|
27
|
+
as a design standard. The workspace is not documentation of the thinking. Done
|
|
28
|
+
right, it is some of the thinking.
|
|
29
|
+
|
|
30
|
+
The philosophers put four conditions around "done right," and they read like a
|
|
31
|
+
practical test. The store is a constant in your life: where the work happens, not
|
|
32
|
+
a destination you remember to visit. Its contents are directly available: a
|
|
33
|
+
store you must dig through fails the condition. What it returns, you trust on
|
|
34
|
+
retrieval. And you trust it because you put it there: everything in it was
|
|
35
|
+
ratified by you at some point in the past. The authors called the fourth
|
|
36
|
+
condition arguable and the first three crucial. For a workspace that asks for
|
|
37
|
+
automatic trust, however, the fourth is what earns the third. Automatic trust is
|
|
38
|
+
reckless for content you did not author, which is how navigation apps route
|
|
39
|
+
trusting drivers into water. It is appropriate for a store of your own past
|
|
40
|
+
judgments, because you know where they came from: you. That is why the compile
|
|
41
|
+
loop ends with your approval, and why anything that came from somewhere else
|
|
42
|
+
stays marked as such. Writing in 1998, the authors scored the technologies of
|
|
43
|
+
their day against the conditions: the Internet failed, and "information in
|
|
44
|
+
certain files on my computer may qualify." Files you own, on a disk you control,
|
|
45
|
+
were the closest thing to qualifying from the start.
|
|
46
|
+
|
|
47
|
+
The builders got there first, independently, which is worth more than any
|
|
48
|
+
citation chain. Bush designed the memex in 1945 and said the essential feature
|
|
49
|
+
was not the storing: "the process of tying two items together is the important
|
|
50
|
+
thing." Luhmann filed roughly ninety thousand cards, published hundreds of
|
|
51
|
+
works, and called the content of the notes secondary to the network of links;
|
|
52
|
+
his box, he said, was a mere container until years of connective work made it a
|
|
53
|
+
partner. His test for partnership is the sharpest available: the partners must
|
|
54
|
+
be able to surprise each other. A workspace that only echoes back what you filed
|
|
55
|
+
is a container. One whose connections return something you did not plan is
|
|
56
|
+
thinking with you.
|
|
57
|
+
|
|
58
|
+
That leaves one practical priority, and the market has it backwards. Capture is
|
|
59
|
+
the cheap half of the loop, and making it cheaper spends effort on the part that
|
|
60
|
+
was never hard; the graveyard of abandoned note systems is capture-heavy and
|
|
61
|
+
synthesis-poor. Retrieval is not judgment. Bringing a note back is not the same
|
|
62
|
+
act as knowing which note matters here. So aim the effort at the expensive half.
|
|
63
|
+
Compile what was caught. Link it to what it contradicts and what it confirms.
|
|
64
|
+
Ratify what earned it. The workspace becomes part of the thinking at the point
|
|
65
|
+
where its structure starts doing work the bare notes cannot.
|
|
66
|
+
|
|
67
|
+
The boundary stays honest. The workspace extends your thinking; it does not
|
|
68
|
+
extend your consciousness, and it does not supply your aims. It holds your cases
|
|
69
|
+
and the record you have made from them. The aim lives in the codex, and the call
|
|
70
|
+
stays yours; the companion principle "Phronesis: Judgment in the Particular"
|
|
71
|
+
names that half. One corollary follows and it is why ownership is a moral stance
|
|
72
|
+
here, not a feature: if the store is genuinely part of your thinking, then losing
|
|
73
|
+
it to a vendor, a format, or a revoked login is closer to an injury than to an
|
|
74
|
+
inconvenience. Nothing that is part of a mind should have a landlord.
|
|
75
|
+
|
|
76
|
+
*Diagnostic: before adding a store or a surface to the trusted path, score it
|
|
77
|
+
against the four conditions. Constantly there? Directly available? Do you trust
|
|
78
|
+
what it gives back? Do you trust it because you approved what went in? A resource
|
|
79
|
+
that fails the conditions is a tool you consult, and consulting tools is fine.
|
|
80
|
+
Only what passes is part of the thinking, and only what is part of the thinking
|
|
81
|
+
deserves automatic trust.*
|
|
82
|
+
|
|
83
|
+
*Source: Clark and Chalmers, "The Extended Mind" (Analysis, 1998) for the parity
|
|
84
|
+
argument and the four conditions; Record and Miller (2018) for the trust
|
|
85
|
+
inversion on unauthored content; Bush, "As We May Think" (1945) and Luhmann,
|
|
86
|
+
"Communicating with Slip Boxes" (1981) for the tradition's own verdict that
|
|
87
|
+
capture is secondary to connection; the retention literature on abandoned
|
|
88
|
+
capture-first systems. The parity thesis is used here as a design standard:
|
|
89
|
+
contested philosophy that has survived its critics, not settled science.*
|
|
90
|
+
|
|
91
|
+
## Interview Prompt
|
|
92
|
+
|
|
93
|
+
Name the one external place you already trust without double-checking: a
|
|
94
|
+
notebook, a file, a list you never second-guess. What earned it that trust? Hold
|
|
95
|
+
onto the answer. Whatever that standard is, it is the standard this workspace has
|
|
96
|
+
to meet before it deserves the same.
|
|
@@ -6,10 +6,10 @@ against `specs/hooks.md` "Surface adapters".
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Codex native lifecycle hooks run the same Phronesis reinforcement
|
|
9
|
+
Codex native lifecycle hooks run the same Phronesis reinforcement logic as
|
|
10
10
|
Claude Code. The JSON config points each Codex event at a small launcher in
|
|
11
|
-
`./hooks/`; each launcher resolves the installation root and
|
|
12
|
-
|
|
11
|
+
`./hooks/`; each launcher resolves the installation root and calls the matching
|
|
12
|
+
event on the CLI's single `hook` interface.
|
|
13
13
|
|
|
14
14
|
Keep `hooks.json` schema-pure. Codex parses hook config strictly: unknown JSON
|
|
15
15
|
fields are fatal at every level, and there is no JSON comment mechanism.
|
|
@@ -19,9 +19,8 @@ Documentation belongs in this README or in script comments, never in
|
|
|
19
19
|
## Root Resolution
|
|
20
20
|
|
|
21
21
|
Codex has no `$CLAUDE_PROJECT_DIR` equivalent and runs hooks at the session
|
|
22
|
-
working directory. Each launcher
|
|
23
|
-
|
|
24
|
-
script in `<install>/.phronesis/hooks/`.
|
|
22
|
+
working directory. Each launcher resolves the installation root with
|
|
23
|
+
`hooks/_resolve.sh`, then execs `phronesis hook <event> --surface codex`.
|
|
25
24
|
|
|
26
25
|
The resolver anchors on the launcher location first, then falls back to walking
|
|
27
26
|
up to the nearest `.phronesis/` marker. That keeps the launcher tied to its own
|
|
@@ -53,13 +52,13 @@ does not force a re-trust.
|
|
|
53
52
|
|
|
54
53
|
## Wiring
|
|
55
54
|
|
|
56
|
-
| Codex event | Launcher |
|
|
55
|
+
| Codex event | Launcher | CLI event | Output role |
|
|
57
56
|
|---|---|---|---|
|
|
58
|
-
| `SessionStart` | `.codex/hooks/session-start.sh` |
|
|
59
|
-
| `UserPromptSubmit` | `.codex/hooks/recall-guard.sh` |
|
|
60
|
-
| `Stop` | `.codex/hooks/session-sweep.sh` |
|
|
61
|
-
| `SubagentStop` | `.codex/hooks/session-sweep-subagent.sh` |
|
|
62
|
-
| `PreCompact` | `.codex/hooks/session-sweep-precompact.sh` |
|
|
57
|
+
| `SessionStart` | `.codex/hooks/session-start.sh` | `session-start` | Due-check `additionalContext` |
|
|
58
|
+
| `UserPromptSubmit` | `.codex/hooks/recall-guard.sh` | `recall-guard` | Recall guard `additionalContext` |
|
|
59
|
+
| `Stop` | `.codex/hooks/session-sweep.sh` | `session-sweep` | Capture sweep `decision:block` plus `reason` |
|
|
60
|
+
| `SubagentStop` | `.codex/hooks/session-sweep-subagent.sh` | `session-sweep-subagent` | Subagent sweep variant |
|
|
61
|
+
| `PreCompact` | `.codex/hooks/session-sweep-precompact.sh` | `session-sweep-precompact` | Compaction reminder |
|
|
63
62
|
|
|
64
63
|
The commands in `hooks.json` are bare relative paths with no arguments. The
|
|
65
64
|
variant is baked into each launcher, so the wiring works whether Codex execs the
|
|
@@ -67,7 +66,7 @@ command directly or through a shell.
|
|
|
67
66
|
|
|
68
67
|
## Runtime-Free Degrade
|
|
69
68
|
|
|
70
|
-
Every launcher
|
|
71
|
-
`PATH
|
|
72
|
-
posture is part of change 0013: missing runtime means an
|
|
73
|
-
noisy hook failure.
|
|
69
|
+
Every launcher resolves `~/.phronesis/bin/phronesis` first, then a `phronesis`
|
|
70
|
+
on `PATH`, and exits cleanly with no output when neither exists. That
|
|
71
|
+
runtime-free no-op posture is part of change 0013: missing runtime means an
|
|
72
|
+
honest gap, not a noisy hook failure.
|
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# does it fall back to a nearest-.phronesis walk-up from cwd. The launcher still verifies the
|
|
13
|
-
# target script before exec, so an incomplete install degrades to a clean no-op, never a
|
|
14
|
-
# noisy `env: … No such file` (exit 127).
|
|
15
|
-
command -v phronesis >/dev/null 2>&1 || exit 0
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Sourced by each Codex launcher: managed-first CLI binding plus own-location install root.
|
|
3
|
+
PHRONESIS_BIN="$HOME/.phronesis/bin/phronesis"
|
|
4
|
+
PHRONESIS_MANAGED_BIN="$PHRONESIS_BIN"
|
|
5
|
+
if [ -f "$PHRONESIS_BIN" ] && [ -x "$PHRONESIS_BIN" ] && [ ! -L "$PHRONESIS_BIN" ]; then
|
|
6
|
+
:
|
|
7
|
+
else
|
|
8
|
+
PHRONESIS_BIN="$(command -v phronesis 2>/dev/null || true)"
|
|
9
|
+
[ -n "$PHRONESIS_BIN" ] || exit 0
|
|
10
|
+
[ "$PHRONESIS_BIN" != "$PHRONESIS_MANAGED_BIN" ] || exit 0
|
|
11
|
+
fi
|
|
16
12
|
|
|
17
|
-
# Bare ${BASH_SOURCE:-$0},
|
|
18
|
-
# dash (the `sh <path>` interpreter on Linux/CI) rejects with "Bad substitution", and this file
|
|
19
|
-
# is SOURCED by a launcher that may itself be running under dash (core-0034 review #1, round 3).
|
|
20
|
-
# Under bash, bare $BASH_SOURCE is array element 0 = THIS file's path (sourced-frame aware).
|
|
21
|
-
# Under POSIX sh it is unset → $0, which in a sourced frame is the *launcher's* $0 — but the
|
|
22
|
-
# launcher is co-located with this resolver in the same .codex/hooks/ dir, so `dirname` lands on
|
|
23
|
-
# that dir either way, and the four-levels-up anchor below is identical.
|
|
13
|
+
# Bare ${BASH_SOURCE:-$0}, never the dash-invalid array-subscript form.
|
|
24
14
|
__src="${BASH_SOURCE:-$0}"
|
|
25
|
-
|
|
15
|
+
case "$__src" in
|
|
16
|
+
*/*) __dir="${__src%/*}" ;;
|
|
17
|
+
*) __dir="." ;;
|
|
18
|
+
esac
|
|
19
|
+
__here="$(cd "$__dir" 2>/dev/null && pwd || true)"
|
|
26
20
|
PHRONESIS_ROOT=""
|
|
27
21
|
if [ -n "$__here" ]; then
|
|
28
22
|
__anchor="$(cd "$__here/../../../.." 2>/dev/null && pwd || true)"
|
|
@@ -30,7 +24,14 @@ if [ -n "$__here" ]; then
|
|
|
30
24
|
fi
|
|
31
25
|
if [ -z "$PHRONESIS_ROOT" ]; then
|
|
32
26
|
__d="$PWD"
|
|
33
|
-
while [ "$__d" != "/" ] && [ ! -d "$__d/.phronesis" ]; do
|
|
27
|
+
while [ "$__d" != "/" ] && [ ! -d "$__d/.phronesis" ]; do
|
|
28
|
+
case "$__d" in
|
|
29
|
+
*/*) __d="${__d%/*}"; [ -n "$__d" ] || __d="/" ;;
|
|
30
|
+
*) __d="/" ;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
34
33
|
[ -d "$__d/.phronesis" ] && PHRONESIS_ROOT="$__d"
|
|
35
34
|
fi
|
|
36
35
|
[ -n "$PHRONESIS_ROOT" ] || exit 0
|
|
36
|
+
PHRONESIS_DIR="$PHRONESIS_ROOT"
|
|
37
|
+
export PHRONESIS_BIN PHRONESIS_DIR
|
|
@@ -1,30 +1,5 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# the event off on stdin to the SHARED, surface-aware recall-guard.sh in
|
|
7
|
-
# <install>/.phronesis/hooks/, marking PHRONESIS_SURFACE=codex. The .codex/hooks.json wires
|
|
8
|
-
# this as a BARE no-arg command path (the variant flag, if any, is baked into the launcher),
|
|
9
|
-
# so the wiring holds whether Codex execs directly or via a shell — one script set, branch
|
|
10
|
-
# on surface (specs/hooks.md §"Surface adapters").
|
|
11
|
-
#
|
|
12
|
-
# Runtime-free / no-install / incomplete-install degrade (change 0013): exit 0, no output —
|
|
13
|
-
# an honest gap, never a noisy error. _resolve.sh exits 0 when phronesis is off PATH or no
|
|
14
|
-
# install resolves; the target-exists check below covers an incomplete install.
|
|
15
|
-
# `set -u` only (no pipefail): no pipelines here, and `set -o pipefail` aborts noisily under
|
|
16
|
-
# a strict POSIX /bin/sh if Codex execs the bare command via `sh <path>` (core-0034 review
|
|
17
|
-
# #2). The exec'd shared script keeps its own bash shebang + pipefail.
|
|
18
|
-
#
|
|
19
|
-
# The source line below uses bare ${BASH_SOURCE:-$0} — NOT the array-subscript "[0]" form: that
|
|
20
|
-
# subscript is a bashism that dash (the `sh <path>` interpreter on Linux/CI) rejects with "Bad
|
|
21
|
-
# substitution" and exits 2 — BEFORE any degrade path — which would defeat the very POSIX-safety
|
|
22
|
-
# the pipefail choice above buys (core-0034 review #1, round 3). Bare $BASH_SOURCE reads array
|
|
23
|
-
# element 0 under bash and is an unset (→ $0) plain var under POSIX sh; both name THIS launcher,
|
|
24
|
-
# and the launcher is co-located with _resolve.sh, so `dirname` lands on the hooks dir either way.
|
|
25
|
-
set -u
|
|
26
|
-
|
|
27
|
-
. "$(cd "$(dirname "${BASH_SOURCE:-$0}")" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
28
|
-
target="$PHRONESIS_ROOT/.phronesis/hooks/recall-guard.sh"
|
|
29
|
-
[ -x "$target" ] || exit 0
|
|
30
|
-
exec env PHRONESIS_SURFACE=codex PHRONESIS_DIR="$PHRONESIS_ROOT" "$target" "$@"
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
__src="${BASH_SOURCE:-$0}"
|
|
3
|
+
case "$__src" in */*) __dir="${__src%/*}" ;; *) __dir="." ;; esac
|
|
4
|
+
. "$(cd "$__dir" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
5
|
+
exec "$PHRONESIS_BIN" hook recall-guard --surface codex
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# in Codex's hookSpecificOutput.additionalContext shape. See recall-guard.sh for the
|
|
7
|
-
# launcher rationale; see _resolve.sh for the cwd-independent, decoy-immune root resolution.
|
|
8
|
-
#
|
|
9
|
-
# Runtime-free / no-install / incomplete-install degrade (change 0013): exit 0, no output.
|
|
10
|
-
# `set -u` only (no pipefail), and bare ${BASH_SOURCE:-$0} not the "[0]"-subscript form — both
|
|
11
|
-
# keep this POSIX /bin/sh-safe (dash rejects the array subscript with "Bad substitution"); see
|
|
12
|
-
# recall-guard.sh (core-0034 #2 / #1-round-3).
|
|
13
|
-
set -u
|
|
14
|
-
|
|
15
|
-
. "$(cd "$(dirname "${BASH_SOURCE:-$0}")" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
16
|
-
target="$PHRONESIS_ROOT/.phronesis/hooks/session-start.sh"
|
|
17
|
-
[ -x "$target" ] || exit 0
|
|
18
|
-
exec env PHRONESIS_SURFACE=codex PHRONESIS_DIR="$PHRONESIS_ROOT" "$target" "$@"
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
__src="${BASH_SOURCE:-$0}"
|
|
3
|
+
case "$__src" in */*) __dir="${__src%/*}" ;; *) __dir="." ;; esac
|
|
4
|
+
. "$(cd "$__dir" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
5
|
+
exec "$PHRONESIS_BIN" hook session-start --surface codex
|
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# Under PHRONESIS_SURFACE=codex the shared script emits the reminder as Codex's
|
|
7
|
-
# {"systemMessage":...} (Claude Code takes the raw line). The variant flag is baked in here,
|
|
8
|
-
# not passed through hooks.json, so the command path stays bare. See recall-guard.sh /
|
|
9
|
-
# _resolve.sh for the launcher mechanics.
|
|
10
|
-
#
|
|
11
|
-
# Runtime-free / no-install / incomplete-install degrade (change 0013): exit 0, no output.
|
|
12
|
-
# `set -u` only (no pipefail), and bare ${BASH_SOURCE:-$0} not the "[0]"-subscript form — both
|
|
13
|
-
# keep this POSIX /bin/sh-safe (dash rejects the array subscript with "Bad substitution"); see
|
|
14
|
-
# recall-guard.sh (core-0034 #2 / #1-round-3).
|
|
15
|
-
set -u
|
|
16
|
-
|
|
17
|
-
. "$(cd "$(dirname "${BASH_SOURCE:-$0}")" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
18
|
-
target="$PHRONESIS_ROOT/.phronesis/hooks/session-sweep.sh"
|
|
19
|
-
[ -x "$target" ] || exit 0
|
|
20
|
-
exec env PHRONESIS_SURFACE=codex PHRONESIS_DIR="$PHRONESIS_ROOT" "$target" --advisory "$@"
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
__src="${BASH_SOURCE:-$0}"
|
|
3
|
+
case "$__src" in */*) __dir="${__src%/*}" ;; *) __dir="." ;; esac
|
|
4
|
+
. "$(cd "$__dir" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
5
|
+
exec "$PHRONESIS_BIN" hook session-sweep-precompact --surface codex
|
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# presently inert — Codex mints no invocation_id (no UserPromptExpansion / matchable Skill
|
|
7
|
-
# PreToolUse, so skill-lifecycle telemetry is Level-2 PARTIAL, change 0028 /
|
|
8
|
-
# event-log.md §"Compliance levels") — so there is nothing to close. It is wired with the
|
|
9
|
-
# SAME script Claude Code uses (not a Codex-specific path) so the boundary is handled
|
|
10
|
-
# correctly if Codex later surfaces the lifecycle; it fabricates nothing meanwhile (emission
|
|
11
|
-
# honesty, change 0021). The variant flag is baked in here, not passed through hooks.json, so
|
|
12
|
-
# the command path stays bare. See recall-guard.sh / _resolve.sh for the launcher mechanics.
|
|
13
|
-
#
|
|
14
|
-
# Runtime-free / no-install / incomplete-install degrade (change 0013): exit 0, no output.
|
|
15
|
-
# `set -u` only (no pipefail), and bare ${BASH_SOURCE:-$0} not the "[0]"-subscript form — both
|
|
16
|
-
# keep this POSIX /bin/sh-safe (dash rejects the array subscript with "Bad substitution"); see
|
|
17
|
-
# recall-guard.sh (core-0034 #2 / #1-round-3).
|
|
18
|
-
set -u
|
|
19
|
-
|
|
20
|
-
. "$(cd "$(dirname "${BASH_SOURCE:-$0}")" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
21
|
-
target="$PHRONESIS_ROOT/.phronesis/hooks/session-sweep.sh"
|
|
22
|
-
[ -x "$target" ] || exit 0
|
|
23
|
-
exec env PHRONESIS_SURFACE=codex PHRONESIS_DIR="$PHRONESIS_ROOT" "$target" --lifecycle-only "$@"
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
__src="${BASH_SOURCE:-$0}"
|
|
3
|
+
case "$__src" in */*) __dir="${__src%/*}" ;; *) __dir="." ;; esac
|
|
4
|
+
. "$(cd "$__dir" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
5
|
+
exec "$PHRONESIS_BIN" hook session-sweep-subagent --surface codex
|
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# ({"decision":"block","reason":...}) and the stop_hook_active loop guard are identical
|
|
7
|
-
# across Claude Code and Codex (spike 2026-06-21), so the shared script needs no Stop-path
|
|
8
|
-
# branch. See recall-guard.sh for the launcher rationale; _resolve.sh for root resolution.
|
|
9
|
-
#
|
|
10
|
-
# Runtime-free / no-install / incomplete-install degrade (change 0013): exit 0, no output.
|
|
11
|
-
# `set -u` only (no pipefail), and bare ${BASH_SOURCE:-$0} not the "[0]"-subscript form — both
|
|
12
|
-
# keep this POSIX /bin/sh-safe (dash rejects the array subscript with "Bad substitution"); see
|
|
13
|
-
# recall-guard.sh (core-0034 #2 / #1-round-3).
|
|
14
|
-
set -u
|
|
15
|
-
|
|
16
|
-
. "$(cd "$(dirname "${BASH_SOURCE:-$0}")" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
17
|
-
target="$PHRONESIS_ROOT/.phronesis/hooks/session-sweep.sh"
|
|
18
|
-
[ -x "$target" ] || exit 0
|
|
19
|
-
exec env PHRONESIS_SURFACE=codex PHRONESIS_DIR="$PHRONESIS_ROOT" "$target" "$@"
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
__src="${BASH_SOURCE:-$0}"
|
|
3
|
+
case "$__src" in */*) __dir="${__src%/*}" ;; *) __dir="." ;; esac
|
|
4
|
+
. "$(cd "$__dir" 2>/dev/null && pwd || echo /nonexistent)/_resolve.sh" 2>/dev/null || exit 0
|
|
5
|
+
exec "$PHRONESIS_BIN" hook session-sweep --surface codex
|