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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/act.js +62 -1
  3. package/src/adapter.js +366 -61
  4. package/src/cli.js +527 -32
  5. package/src/compile.js +141 -10
  6. package/src/doctor.js +63 -2
  7. package/src/hook.js +312 -0
  8. package/src/hooks-refresh.js +1363 -0
  9. package/src/hooks.js +48 -33
  10. package/src/init.js +37 -4
  11. package/src/launcher.js +2105 -0
  12. package/src/layout.js +51 -7
  13. package/src/profile.js +169 -0
  14. package/src/prompts.js +88 -0
  15. package/src/skills-bump.js +88 -20
  16. package/src/skills.js +103 -10
  17. package/templates/codex/INDEX.md +4 -2
  18. package/templates/codex/seed/ai-practice/the-workspace-is-part-of-the-thinking.md +96 -0
  19. package/templates/codex-surface/README.md +15 -16
  20. package/templates/codex-surface/hooks/_resolve.sh +25 -24
  21. package/templates/codex-surface/hooks/recall-guard.sh +5 -30
  22. package/templates/codex-surface/hooks/session-start.sh +5 -18
  23. package/templates/codex-surface/hooks/session-sweep-precompact.sh +5 -20
  24. package/templates/codex-surface/hooks/session-sweep-subagent.sh +5 -23
  25. package/templates/codex-surface/hooks/session-sweep.sh +5 -19
  26. package/templates/launcher/phronesis +95 -0
  27. package/templates/phronesis-hooks/compile-active-context.sh +12 -38
  28. package/templates/phronesis-hooks/recall-guard.sh +12 -103
  29. package/templates/phronesis-hooks/session-start.sh +10 -88
  30. package/templates/phronesis-hooks/session-sweep.sh +16 -139
  31. package/templates/phronesis-hooks/skill-lifecycle.sh +12 -37
  32. package/templates/skills/lint/SKILL.md +7 -2
  33. package/templates/skills/onboard/SKILL.md +47 -9
  34. package/templates/skills/onboard/evals/rubric.md +5 -3
  35. package/templates/skills/prd-draft/SKILL.md +34 -26
  36. package/templates/skills/prd-draft/evals/rubric.md +1 -1
  37. package/vendor/core/src/action-registry.js +6 -3
  38. package/vendor/core/src/actions.js +120 -16
  39. package/vendor/core/src/index.js +3 -0
  40. package/vendor/core/src/lint.js +22 -5
  41. 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
- return createHash('sha256').update(String(value), 'utf8').digest('hex');
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 signedness from an
162
- // explicit marker only (signing authority is T7.0/T7.6; absent provisional, never a
163
- // false green).
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
- const files = (await readdir(dir)).filter((f) => /^golden-.*\.json$/.test(f)).sort();
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
- goldens.push({ ...d, id, path: p });
199
- idParts.push(`${id}:${sha256(raw)}`);
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
- let signed = false;
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,
@@ -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 via `promote_to_codex`, passing the seed as a `--json` payload:_
11
- _`--json '{"from_seed":"<collection>/<slug>","rationale":""}'` (`act` takes only `--json`)._
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 scripts as
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 hands off to the
12
- shared script under `<install>/.phronesis/hooks/`.
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 marks `PHRONESIS_SURFACE=codex`, resolves the
23
- installation root with `hooks/_resolve.sh`, then execs the matching shared
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 | Shared script | Output role |
55
+ | Codex event | Launcher | CLI event | Output role |
57
56
  |---|---|---|---|
58
- | `SessionStart` | `.codex/hooks/session-start.sh` | `.phronesis/hooks/session-start.sh` | Due-check `additionalContext` |
59
- | `UserPromptSubmit` | `.codex/hooks/recall-guard.sh` | `.phronesis/hooks/recall-guard.sh` | Recall guard `additionalContext` |
60
- | `Stop` | `.codex/hooks/session-sweep.sh` | `.phronesis/hooks/session-sweep.sh` | Capture sweep `decision:block` plus `reason` |
61
- | `SubagentStop` | `.codex/hooks/session-sweep-subagent.sh` | `.phronesis/hooks/session-sweep.sh` | Subagent sweep variant |
62
- | `PreCompact` | `.codex/hooks/session-sweep-precompact.sh` | `.phronesis/hooks/session-sweep.sh` | Compaction reminder |
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 exits cleanly with no output when no `phronesis` runtime is on
71
- `PATH` or when its target shared script is missing. That runtime-free no-op
72
- posture is part of change 0013: missing runtime means an honest gap, not a
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
- #!/usr/bin/env bash
2
- # Phronesis Codex launcher resolver SOURCED by the .codex/hooks/*.sh launchers (change
3
- # 0028 / core-0034). Sets PHRONESIS_ROOT to the installation root, or `exit 0` (a clean
4
- # runtime-free / no-install degrade that the sourcing launcher inherits).
5
- #
6
- # Resolution anchors on THIS file's own emitted location — it ships at
7
- # <root>/domains/{slug}/.codex/hooks/_resolve.sh, a fixed FOUR levels under the install
8
- # root so it is independent of the session cwd AND immune to a stray .phronesis/ ancestor
9
- # (a second checkout, or a domain workspace copied out of its install). A blind
10
- # walk-up-from-cwd would capture the wrong install in those cases. Only if that fixed anchor
11
- # does not resolve to an install (e.g. the open target was relocated to a different depth)
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}, NOT the array-subscript "[0]" form: that subscript is a bashism that
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
- __here="$(cd "$(dirname "$__src")" 2>/dev/null && pwd || true)"
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 __d="$(dirname "$__d")"; done
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
- #!/usr/bin/env bash
2
- # Phronesis Codex surface launcher → recall-guard (UserPromptSubmit).
3
- #
4
- # Resolves the install root via the sibling _resolve.sh (sourced it anchors on the
5
- # launcher's own emitted location, so it is cwd-independent and decoy-immune), then hands
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
- #!/usr/bin/env bash
2
- # Phronesis Codex surface launcher → session-start due-check (SessionStart).
3
- #
4
- # Resolves the install root via the sibling _resolve.sh (sourced) and hands off to the
5
- # SHARED session-start.sh, which under PHRONESIS_SURFACE=codex wraps the due-check printout
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
- #!/usr/bin/env bash
2
- # Phronesis Codex surface launcher → pre-compaction capture reminder (PreCompact).
3
- #
4
- # The --advisory variant of the shared session-sweep.sh: it cannot block, so it surfaces a
5
- # best-effort "review the conversation for unstored context before it compacts" reminder.
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
- #!/usr/bin/env bash
2
- # Phronesis Codex surface launcher → subagent-boundary sweep (SubagentStop).
3
- #
4
- # The --lifecycle-only variant of the shared session-sweep.sh: it skips the capture sweep
5
- # and only closes an open skill-lifecycle record for the subagent boundary. On Codex this is
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
- #!/usr/bin/env bash
2
- # Phronesis Codex surface launcher → capture sweep (Stop).
3
- #
4
- # Resolves the install root via the sibling _resolve.sh (sourced) and hands the Stop event
5
- # off on stdin to the SHARED session-sweep.sh. The block-continue output
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