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/cli.js
CHANGED
|
@@ -4,7 +4,6 @@ import { createRequire } from 'node:module';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { resolve } from 'node:path';
|
|
6
6
|
import { realpathSync } from 'node:fs';
|
|
7
|
-
import { DEFAULT_DOMAIN } from './layout.js';
|
|
8
7
|
import { COVERAGE_FLOOR_RANGE, MAX_K, QUALITY_FLOOR_RANGE } from './skills-bump.js';
|
|
9
8
|
|
|
10
9
|
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
@@ -21,6 +20,8 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
21
20
|
if (!cmd || cmd === '--help' || cmd === '-h') return { command: 'help' };
|
|
22
21
|
if (cmd === '--version' || cmd === '-v') return { command: 'version' };
|
|
23
22
|
if (cmd === 'init') return parseInitArgs(rest);
|
|
23
|
+
if (cmd === 'launcher') return parseLauncherArgs(rest);
|
|
24
|
+
if (cmd === 'profile') return parseProfileArgs(rest);
|
|
24
25
|
if (cmd === 'export') return parseExportArgs(rest);
|
|
25
26
|
if (cmd === 'validate') return parseValidateArgs(rest);
|
|
26
27
|
if (cmd === 'links') return parseLinksArgs(rest);
|
|
@@ -32,6 +33,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
32
33
|
if (cmd === 'active-context') return parseActiveContextArgs(rest);
|
|
33
34
|
if (cmd === 'registry') return parseRegistryArgs(rest);
|
|
34
35
|
if (cmd === 'skills') return parseSkillsArgs(rest);
|
|
36
|
+
if (cmd === 'hook') return parseHookArgs(rest);
|
|
35
37
|
if (cmd === 'hooks') return parseHooksArgs(rest);
|
|
36
38
|
if (cmd === 'adapter') return parseAdapterArgs(rest);
|
|
37
39
|
if (cmd === 'guard') return parseGuardArgs(rest);
|
|
@@ -48,6 +50,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
48
50
|
function parseInitArgs(args) {
|
|
49
51
|
let dir;
|
|
50
52
|
let force = false;
|
|
53
|
+
let persist;
|
|
51
54
|
// domain + the minimal-profile value flags (core-0039). Each accepts `--flag value` and `--flag=value`.
|
|
52
55
|
const VALUE_FLAGS = ['domain', 'name', 'role', 'company', 'working-style'];
|
|
53
56
|
const values = {};
|
|
@@ -59,6 +62,14 @@ function parseInitArgs(args) {
|
|
|
59
62
|
force = true;
|
|
60
63
|
continue;
|
|
61
64
|
}
|
|
65
|
+
if (a === '--persist' || a === '--no-persist') {
|
|
66
|
+
const requested = a === '--persist';
|
|
67
|
+
if (persist !== undefined && persist !== requested) {
|
|
68
|
+
return { command: 'invalid', error: 'init: --persist and --no-persist are mutually exclusive.' };
|
|
69
|
+
}
|
|
70
|
+
persist = requested;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
62
73
|
let matched = false;
|
|
63
74
|
for (const f of VALUE_FLAGS) {
|
|
64
75
|
if (a === `--${f}`) {
|
|
@@ -92,6 +103,168 @@ function parseInitArgs(args) {
|
|
|
92
103
|
role: values.role,
|
|
93
104
|
company: values.company,
|
|
94
105
|
workingStyle: values['working-style'],
|
|
106
|
+
persist,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseLauncherArgs(args) {
|
|
111
|
+
const [sub, ...rest] = args;
|
|
112
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'launcher-help' };
|
|
113
|
+
if (sub === 'install') {
|
|
114
|
+
const parsed = parseOptions(rest, {
|
|
115
|
+
values: ['backend', 'app-target', 'app-runtime', 'app-version', 'expect-owner', 'expect-version'],
|
|
116
|
+
flags: ['force-launcher', 'if-owned', 'no-path-offer', 'expect-absent'],
|
|
117
|
+
});
|
|
118
|
+
if (parsed.help) return { command: 'launcher-help' };
|
|
119
|
+
if (parsed.error) return { command: 'invalid', error: `${parsed.error} for launcher install.` };
|
|
120
|
+
if (parsed.positionals.length) {
|
|
121
|
+
return { command: 'invalid', error: 'launcher install takes no positional arguments.' };
|
|
122
|
+
}
|
|
123
|
+
const backend = parsed.values.backend || 'self-copy';
|
|
124
|
+
if (!['app', 'self-copy'].includes(backend)) {
|
|
125
|
+
return { command: 'invalid', error: `launcher install: --backend must be app | self-copy (got ${JSON.stringify(backend)}).` };
|
|
126
|
+
}
|
|
127
|
+
const appValues = ['app-target', 'app-runtime', 'app-version'];
|
|
128
|
+
if (backend === 'app') {
|
|
129
|
+
const missing = ['app-target', 'app-runtime'].filter((name) => !parsed.values[name]);
|
|
130
|
+
if (missing.length) {
|
|
131
|
+
return { command: 'invalid', error: `launcher install --backend app requires ${missing.map((name) => `--${name}`).join(', ')}.` };
|
|
132
|
+
}
|
|
133
|
+
} else if (appValues.some((name) => parsed.values[name] !== undefined) || parsed.flags['if-owned']) {
|
|
134
|
+
return { command: 'invalid', error: 'launcher install: app target/runtime/version and --if-owned require --backend app.' };
|
|
135
|
+
}
|
|
136
|
+
const expectOwner = parsed.values['expect-owner'];
|
|
137
|
+
const expectVersion = parsed.values['expect-version'];
|
|
138
|
+
const expectAbsent = Boolean(parsed.flags['expect-absent']);
|
|
139
|
+
if (parsed.flags['if-owned'] && (expectAbsent || expectOwner !== undefined || expectVersion !== undefined)) {
|
|
140
|
+
return { command: 'invalid', error: 'launcher install: --if-owned cannot be combined with --expect-absent/--expect-owner/--expect-version.' };
|
|
141
|
+
}
|
|
142
|
+
if (expectAbsent && (expectOwner !== undefined || expectVersion !== undefined)) {
|
|
143
|
+
return { command: 'invalid', error: 'launcher install: --expect-absent cannot be combined with --expect-owner/--expect-version.' };
|
|
144
|
+
}
|
|
145
|
+
if ((expectOwner === undefined) !== (expectVersion === undefined)) {
|
|
146
|
+
return { command: 'invalid', error: 'launcher install: --expect-owner and --expect-version must be passed together.' };
|
|
147
|
+
}
|
|
148
|
+
if (expectOwner !== undefined && !['app', 'self-copy'].includes(expectOwner)) {
|
|
149
|
+
return { command: 'invalid', error: `launcher install: --expect-owner must be app | self-copy (got ${JSON.stringify(expectOwner)}).` };
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
command: 'launcher-install',
|
|
153
|
+
forceLauncher: Boolean(parsed.flags['force-launcher']),
|
|
154
|
+
backend,
|
|
155
|
+
appTarget: parsed.values['app-target'],
|
|
156
|
+
appRuntime: parsed.values['app-runtime'],
|
|
157
|
+
appVersion: parsed.values['app-version'],
|
|
158
|
+
ifOwned: Boolean(parsed.flags['if-owned']),
|
|
159
|
+
offerPath: !parsed.flags['no-path-offer'],
|
|
160
|
+
expectedPrior: expectAbsent
|
|
161
|
+
? { absent: true }
|
|
162
|
+
: expectOwner === undefined
|
|
163
|
+
? null
|
|
164
|
+
: { owner: expectOwner, version: expectVersion },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
if (sub === 'remove') {
|
|
168
|
+
const parsed = parseOptions(rest, { values: ['backend'], flags: ['force-launcher'] });
|
|
169
|
+
if (parsed.help) return { command: 'launcher-help' };
|
|
170
|
+
if (parsed.error) return { command: 'invalid', error: `${parsed.error} for launcher remove.` };
|
|
171
|
+
if (parsed.positionals.length) {
|
|
172
|
+
return { command: 'invalid', error: 'launcher remove takes no positional arguments.' };
|
|
173
|
+
}
|
|
174
|
+
const backend = parsed.values.backend || 'self-copy';
|
|
175
|
+
if (!['app', 'self-copy'].includes(backend)) {
|
|
176
|
+
return { command: 'invalid', error: `launcher remove: --backend must be app | self-copy (got ${JSON.stringify(backend)}).` };
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
command: 'launcher-remove',
|
|
180
|
+
forceLauncher: Boolean(parsed.flags['force-launcher']),
|
|
181
|
+
backend,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (sub === 'path') {
|
|
185
|
+
const parsed = parseOptions(rest, { flags: ['append', 'append-consented-by-surface', 'json'] });
|
|
186
|
+
if (parsed.help) return { command: 'launcher-help' };
|
|
187
|
+
if (parsed.error) return { command: 'invalid', error: `${parsed.error} for launcher path.` };
|
|
188
|
+
if (parsed.positionals.length) {
|
|
189
|
+
return { command: 'invalid', error: 'launcher path takes no positional arguments.' };
|
|
190
|
+
}
|
|
191
|
+
if (parsed.flags['append-consented-by-surface'] && !parsed.flags.append) {
|
|
192
|
+
return { command: 'invalid', error: 'launcher path: --append-consented-by-surface requires --append.' };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
command: 'launcher-path',
|
|
196
|
+
append: Boolean(parsed.flags.append),
|
|
197
|
+
surfaceConsented: Boolean(parsed.flags['append-consented-by-surface']),
|
|
198
|
+
json: Boolean(parsed.flags.json),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (sub === 'status') {
|
|
202
|
+
const parsed = parseOptions(rest, { flags: ['json'] });
|
|
203
|
+
if (parsed.help) return { command: 'launcher-help' };
|
|
204
|
+
if (parsed.error) return { command: 'invalid', error: `${parsed.error} for launcher status.` };
|
|
205
|
+
if (parsed.positionals.length) {
|
|
206
|
+
return { command: 'invalid', error: 'launcher status takes no positional arguments.' };
|
|
207
|
+
}
|
|
208
|
+
return { command: 'launcher-status', json: Boolean(parsed.flags.json) };
|
|
209
|
+
}
|
|
210
|
+
return { command: 'invalid', error: `Unknown launcher subcommand: ${sub} (expected: install | remove | path | status).` };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// `phronesis profile set` (core-0062 D1) — the non-destructive repair verb for the stamped
|
|
214
|
+
// operator profile. Same value-flag grammar as init (--flag value / --flag=value); no --force.
|
|
215
|
+
function parseProfileArgs(args) {
|
|
216
|
+
const [sub, ...rest] = args;
|
|
217
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'profile-help' };
|
|
218
|
+
if (sub !== 'set') return { command: 'invalid', error: `Unknown profile subcommand: ${sub} (expected: set).` };
|
|
219
|
+
|
|
220
|
+
let dir;
|
|
221
|
+
const VALUE_FLAGS = ['domain', 'name', 'role', 'company', 'working-style'];
|
|
222
|
+
const values = {};
|
|
223
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
224
|
+
const a = rest[i];
|
|
225
|
+
if (a === '--help' || a === '-h') return { command: 'profile-help' };
|
|
226
|
+
let matched = false;
|
|
227
|
+
for (const f of VALUE_FLAGS) {
|
|
228
|
+
if (a === `--${f}`) {
|
|
229
|
+
const v = rest[i + 1];
|
|
230
|
+
if (v === undefined || v.startsWith('--')) return { command: 'invalid', error: `Missing value for --${f}` };
|
|
231
|
+
values[f] = v;
|
|
232
|
+
i += 1;
|
|
233
|
+
matched = true;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (a.startsWith(`--${f}=`)) {
|
|
237
|
+
values[f] = a.slice(`--${f}=`.length);
|
|
238
|
+
matched = true;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (matched) continue;
|
|
243
|
+
if (a === '--dir') {
|
|
244
|
+
const v = rest[i + 1];
|
|
245
|
+
if (v === undefined || v.startsWith('--')) return { command: 'invalid', error: 'Missing value for --dir' };
|
|
246
|
+
dir = v;
|
|
247
|
+
i += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (a.startsWith('--dir=')) {
|
|
251
|
+
dir = a.slice('--dir='.length);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (a.startsWith('-')) return { command: 'invalid', error: `Unknown option for profile set: ${a}` };
|
|
255
|
+
return { command: 'invalid', error: 'profile set takes no positional arguments (use --name, --role, --company, --working-style).' };
|
|
256
|
+
}
|
|
257
|
+
if (values.name === undefined && values.role === undefined && values.company === undefined && values['working-style'] === undefined) {
|
|
258
|
+
return { command: 'invalid', error: 'profile set: pass at least one of --name, --role, --company, --working-style.' };
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
command: 'profile-set',
|
|
262
|
+
dir,
|
|
263
|
+
domain: values.domain,
|
|
264
|
+
name: values.name,
|
|
265
|
+
role: values.role,
|
|
266
|
+
company: values.company,
|
|
267
|
+
workingStyle: values['working-style'],
|
|
95
268
|
};
|
|
96
269
|
}
|
|
97
270
|
|
|
@@ -197,8 +370,9 @@ function parseLinksArgs(args) {
|
|
|
197
370
|
// Compact option parser shared by the event commands (`--name value` / `--name=value` for
|
|
198
371
|
// `values`, presence flags for `flags`, the rest positional). The older parsers above predate
|
|
199
372
|
// it and stay hand-rolled; new multi-option commands use this to avoid the per-flag boilerplate.
|
|
200
|
-
function parseOptions(args, { values = [], flags = [] } = {}) {
|
|
373
|
+
function parseOptions(args, { values = [], multiValues = [], flags = [] } = {}) {
|
|
201
374
|
const valueSet = new Set(values);
|
|
375
|
+
const multiValueSet = new Set(multiValues);
|
|
202
376
|
const flagSet = new Set(flags);
|
|
203
377
|
const out = { values: {}, flags: {}, positionals: [] };
|
|
204
378
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -208,10 +382,11 @@ function parseOptions(args, { values = [], flags = [] } = {}) {
|
|
|
208
382
|
const eq = a.indexOf('=');
|
|
209
383
|
const name = eq === -1 ? a.slice(2) : a.slice(2, eq);
|
|
210
384
|
if (flagSet.has(name)) {
|
|
385
|
+
if (eq !== -1) return { error: `Presence flag --${name} does not accept a value` };
|
|
211
386
|
out.flags[name] = true;
|
|
212
387
|
continue;
|
|
213
388
|
}
|
|
214
|
-
if (valueSet.has(name)) {
|
|
389
|
+
if (valueSet.has(name) || multiValueSet.has(name)) {
|
|
215
390
|
if (eq === -1) {
|
|
216
391
|
// A bare `--name` at the end, or one followed by another `--option`, is a missing
|
|
217
392
|
// value — not "consume the next token". Swallowing it silently mislabels the next
|
|
@@ -220,10 +395,17 @@ function parseOptions(args, { values = [], flags = [] } = {}) {
|
|
|
220
395
|
if (next === undefined || next.startsWith('--')) {
|
|
221
396
|
return { error: `Missing value for --${name}` };
|
|
222
397
|
}
|
|
223
|
-
|
|
398
|
+
if (multiValueSet.has(name)) {
|
|
399
|
+
if (!Array.isArray(out.values[name])) out.values[name] = [];
|
|
400
|
+
out.values[name].push(next);
|
|
401
|
+
} else out.values[name] = next;
|
|
224
402
|
i += 1;
|
|
225
403
|
} else {
|
|
226
|
-
|
|
404
|
+
const value = a.slice(eq + 1);
|
|
405
|
+
if (multiValueSet.has(name)) {
|
|
406
|
+
if (!Array.isArray(out.values[name])) out.values[name] = [];
|
|
407
|
+
out.values[name].push(value);
|
|
408
|
+
} else out.values[name] = value;
|
|
227
409
|
}
|
|
228
410
|
continue;
|
|
229
411
|
}
|
|
@@ -278,12 +460,26 @@ function parseEventArgs(args) {
|
|
|
278
460
|
// and is rejected); flags carry execution context only. The approval signal is env
|
|
279
461
|
// (PHRONESIS_APPROVAL_SOURCE), set by an approving adapter — deliberately not a flag.
|
|
280
462
|
function parseActArgs(args) {
|
|
281
|
-
const p = parseOptions(args, {
|
|
463
|
+
const p = parseOptions(args, {
|
|
464
|
+
values: ['json', 'session', 'domain', 'actor', 'surface', 'dir', 'invocation-id'],
|
|
465
|
+
flags: ['schema'],
|
|
466
|
+
});
|
|
282
467
|
if (p.help) return { command: 'act-help' };
|
|
283
468
|
if (p.error) return { command: 'invalid', error: `${p.error} for act.` };
|
|
284
469
|
if (p.positionals.length > 1) {
|
|
285
470
|
return { command: 'invalid', error: 'act takes one <type> argument (semantic inputs go inside --json).' };
|
|
286
471
|
}
|
|
472
|
+
// `--schema` is a read-only introspection of the effective payload schema — it does not
|
|
473
|
+
// run the action, so it is mutually exclusive with `--json` (describe vs execute).
|
|
474
|
+
if (p.flags.schema) {
|
|
475
|
+
if (p.values.json !== undefined) {
|
|
476
|
+
return {
|
|
477
|
+
command: 'invalid',
|
|
478
|
+
error: 'act --schema and --json are mutually exclusive: --schema prints the payload contract, --json executes against it.',
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return { command: 'act-schema', type: p.positionals[0], dir: p.values.dir };
|
|
482
|
+
}
|
|
287
483
|
const parsed = {
|
|
288
484
|
command: 'act',
|
|
289
485
|
type: p.positionals[0],
|
|
@@ -334,7 +530,7 @@ function parseCompileArgs(args) {
|
|
|
334
530
|
// accept/reject are operator-driven by contract — no --actor (the events are always
|
|
335
531
|
// actor: operator; the approval signal rides env PHRONESIS_APPROVAL_SOURCE, like act).
|
|
336
532
|
if (sub === 'accept') {
|
|
337
|
-
const p = parseOptions(rest, { values: ['session', 'surface', 'dir'] });
|
|
533
|
+
const p = parseOptions(rest, { values: ['session', 'surface', 'dir', 'reviewed-digest'] });
|
|
338
534
|
if (p.help) return { command: 'compile-help' };
|
|
339
535
|
if (p.error) return { command: 'invalid', error: `${p.error} for compile accept.` };
|
|
340
536
|
if (p.positionals.length !== 1) {
|
|
@@ -346,6 +542,9 @@ function parseCompileArgs(args) {
|
|
|
346
542
|
session: p.values.session,
|
|
347
543
|
surface: p.values.surface,
|
|
348
544
|
dir: p.values.dir,
|
|
545
|
+
// --reviewed-digest <sha>: refuse in-child if the draft's current bytes differ from the bytes the
|
|
546
|
+
// operator reviewed (closes the accept byte-race at the true promotion boundary).
|
|
547
|
+
reviewedDigest: p.values['reviewed-digest'],
|
|
349
548
|
};
|
|
350
549
|
}
|
|
351
550
|
if (sub === 'reject') {
|
|
@@ -366,7 +565,7 @@ function parseCompileArgs(args) {
|
|
|
366
565
|
}
|
|
367
566
|
if (sub === 'review') {
|
|
368
567
|
const p = parseOptions(rest, {
|
|
369
|
-
values: ['domain', 'session', 'surface', 'dir', 'rationale'],
|
|
568
|
+
values: ['domain', 'session', 'surface', 'dir', 'rationale', 'approve-only'],
|
|
370
569
|
flags: ['approve-all', 'resume', 'json'],
|
|
371
570
|
});
|
|
372
571
|
if (p.help) return { command: 'compile-help' };
|
|
@@ -382,6 +581,8 @@ function parseCompileArgs(args) {
|
|
|
382
581
|
dir: p.values.dir,
|
|
383
582
|
rationale: p.values.rationale,
|
|
384
583
|
approveAll: Boolean(p.flags['approve-all']),
|
|
584
|
+
// --approve-only <comma-list>: restrict approve-all to a caller-reviewed subset (else all eligible).
|
|
585
|
+
approveOnly: p.values['approve-only'],
|
|
385
586
|
resume: Boolean(p.flags.resume),
|
|
386
587
|
json: Boolean(p.flags.json),
|
|
387
588
|
};
|
|
@@ -441,7 +642,7 @@ function parseSkillsArgs(args) {
|
|
|
441
642
|
if (sub === 'bump') {
|
|
442
643
|
const p = parseOptions(rest, {
|
|
443
644
|
values: ['to', 'repo', 'k', 'coverage-floor', 'quality-floor', 'rationale', 'accepted-by', 'judge-model', 'goldens-dir', 'domain'],
|
|
444
|
-
flags: ['accept-regression', 'rebaseline', 'shape-change', 'json'],
|
|
645
|
+
flags: ['accept-regression', 'rebaseline', 'verify-current', 'shape-change', 'json'],
|
|
445
646
|
});
|
|
446
647
|
if (p.help) return { command: 'skills-help' };
|
|
447
648
|
if (p.error) return { command: 'invalid', error: `${p.error} for skills bump.` };
|
|
@@ -488,6 +689,7 @@ function parseSkillsArgs(args) {
|
|
|
488
689
|
domain: p.values.domain,
|
|
489
690
|
acceptRegression: Boolean(p.flags['accept-regression']),
|
|
490
691
|
rebaseline: Boolean(p.flags.rebaseline),
|
|
692
|
+
verifyCurrent: Boolean(p.flags['verify-current']),
|
|
491
693
|
shapeChange: Boolean(p.flags['shape-change']),
|
|
492
694
|
json: Boolean(p.flags.json),
|
|
493
695
|
};
|
|
@@ -532,6 +734,31 @@ function parseEvalArgs(args) {
|
|
|
532
734
|
function parseHooksArgs(args) {
|
|
533
735
|
const [sub, ...rest] = args;
|
|
534
736
|
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'hooks-help' };
|
|
737
|
+
if (sub === 'refresh') {
|
|
738
|
+
const p = parseOptions(rest, {
|
|
739
|
+
values: ['dir'],
|
|
740
|
+
multiValues: ['apply'],
|
|
741
|
+
flags: ['apply-all', 'persist', 'no-persist'],
|
|
742
|
+
});
|
|
743
|
+
if (p.help) return { command: 'hooks-help' };
|
|
744
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks refresh.` };
|
|
745
|
+
if (p.positionals.length) {
|
|
746
|
+
return { command: 'invalid', error: 'hooks refresh takes no positional arguments.' };
|
|
747
|
+
}
|
|
748
|
+
if (p.flags.persist && p.flags['no-persist']) {
|
|
749
|
+
return { command: 'invalid', error: 'hooks refresh: --persist and --no-persist are mutually exclusive.' };
|
|
750
|
+
}
|
|
751
|
+
if (p.flags['apply-all'] && p.values.apply?.length) {
|
|
752
|
+
return { command: 'invalid', error: 'hooks refresh: --apply and --apply-all are mutually exclusive.' };
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
command: 'hooks-refresh',
|
|
756
|
+
dir: p.values.dir,
|
|
757
|
+
apply: p.values.apply || [],
|
|
758
|
+
applyAll: Boolean(p.flags['apply-all']),
|
|
759
|
+
persist: p.flags.persist ? true : p.flags['no-persist'] ? false : undefined,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
535
762
|
if (sub === 'due-check') {
|
|
536
763
|
const p = parseOptions(rest, { values: ['session', 'dir', 'actor', 'surface'] });
|
|
537
764
|
if (p.help) return { command: 'hooks-help' };
|
|
@@ -618,12 +845,31 @@ function parseHooksArgs(args) {
|
|
|
618
845
|
command: 'invalid',
|
|
619
846
|
error:
|
|
620
847
|
`Unknown hooks subcommand: ${sub} ` +
|
|
621
|
-
'(expected: due-check | sweep-status | skill-start | skill-complete | skill-abandon | skill-sweep-stale).',
|
|
848
|
+
'(expected: refresh | due-check | sweep-status | skill-start | skill-complete | skill-abandon | skill-sweep-stale).',
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function parseHookArgs(args) {
|
|
853
|
+
const p = parseOptions(args, { values: ['surface', 'mode'] });
|
|
854
|
+
if (p.help) return { command: 'hook-help' };
|
|
855
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hook.` };
|
|
856
|
+
if (p.positionals.length !== 1) {
|
|
857
|
+
return { command: 'invalid', error: 'hook takes exactly one <event> argument.' };
|
|
858
|
+
}
|
|
859
|
+
if (!p.values.surface) {
|
|
860
|
+
return { command: 'invalid', error: 'hook requires --surface <surface>.' };
|
|
861
|
+
}
|
|
862
|
+
return {
|
|
863
|
+
command: 'hook',
|
|
864
|
+
event: p.positionals[0],
|
|
865
|
+
surface: p.values.surface,
|
|
866
|
+
mode: p.values.mode,
|
|
622
867
|
};
|
|
623
868
|
}
|
|
624
869
|
|
|
625
870
|
// `adapter test <surface>` — the per-surface adapter self-check (specs/tech-stack.md
|
|
626
|
-
// §"Adapter test command"; change 0028 / core-0034). V1 ships `adapter test codex
|
|
871
|
+
// §"Adapter test command"; change 0028 / core-0034; change 0055). V1 ships `adapter test codex`
|
|
872
|
+
// and `adapter test desktop`.
|
|
627
873
|
function parseAdapterArgs(args) {
|
|
628
874
|
const [sub, ...rest] = args;
|
|
629
875
|
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'adapter-help' };
|
|
@@ -632,7 +878,7 @@ function parseAdapterArgs(args) {
|
|
|
632
878
|
if (p.help) return { command: 'adapter-help' };
|
|
633
879
|
if (p.error) return { command: 'invalid', error: `${p.error} for adapter test.` };
|
|
634
880
|
if (p.positionals.length !== 1) {
|
|
635
|
-
return { command: 'invalid', error: 'adapter test takes one <surface> argument (V1: codex).' };
|
|
881
|
+
return { command: 'invalid', error: 'adapter test takes one <surface> argument (V1: codex, desktop).' };
|
|
636
882
|
}
|
|
637
883
|
return {
|
|
638
884
|
command: 'adapter-test',
|
|
@@ -818,6 +1064,7 @@ Usage: phronesis <command>
|
|
|
818
1064
|
|
|
819
1065
|
Commands:
|
|
820
1066
|
init [dir] Create a Phronesis installation (default dir: phronesis-installation)
|
|
1067
|
+
launcher <sub> Install or remove the managed command dispatcher
|
|
821
1068
|
export [dir] Tar an installation into a portable archive (default dir: current)
|
|
822
1069
|
validate [dir] Check compiled + codex objects against the ontology contract
|
|
823
1070
|
search <query> Stage 1 retrieval — BM25 over active-domain compiled/ + shared codex
|
|
@@ -832,7 +1079,8 @@ Commands:
|
|
|
832
1079
|
registry <sub> Show the install's action-type + hook registry (status)
|
|
833
1080
|
skills <sub> Show the install's skill registry (status)
|
|
834
1081
|
eval <sub> Local eval data viewer (view) — goldens, scores, candidate output
|
|
835
|
-
|
|
1082
|
+
hook <event> Surface hook dispatcher (stdin JSON → surface-shaped stdout)
|
|
1083
|
+
hooks <sub> Refresh installed hook wiring; session-boundary hook support
|
|
836
1084
|
adapter <sub> Per-surface adapter self-check (test codex)
|
|
837
1085
|
guard Prompt-submit recall guard (stdin-only)
|
|
838
1086
|
ingest <source> Stage one URL, file, or stdin paste into domain raw/
|
|
@@ -851,6 +1099,7 @@ function showInitHelp() {
|
|
|
851
1099
|
`
|
|
852
1100
|
Usage: phronesis init [dir] [--force] [--domain <slug>]
|
|
853
1101
|
[--name <name>] [--role <role>] [--company <co>] [--working-style <style>]
|
|
1102
|
+
[--persist | --no-persist]
|
|
854
1103
|
|
|
855
1104
|
Creates a three-layer installation at [dir]:
|
|
856
1105
|
codex/ shared principles across all domains (seed + personal)
|
|
@@ -862,13 +1111,86 @@ install does not load [Name]/[Role] placeholders. On an interactive terminal, a
|
|
|
862
1111
|
missing name/role is prompted; a non-interactive (scripted/CI) init never prompts
|
|
863
1112
|
or blocks — it completes and leaves the placeholders.
|
|
864
1113
|
|
|
1114
|
+
On an interactive terminal, init asks once whether to install the managed
|
|
1115
|
+
\`phronesis\` command for automatic hooks. --persist opts in for scripts;
|
|
1116
|
+
--no-persist suppresses the interactive persistence prompt.
|
|
1117
|
+
|
|
865
1118
|
Options:
|
|
866
1119
|
--force, -f Replace a non-empty target directory
|
|
867
1120
|
--domain <slug> Domain workspace to create (default: pm)
|
|
868
|
-
--name <name> Operator name (fills USER.md + AGENTS.md §1)
|
|
869
|
-
--role <role> Operator role
|
|
870
|
-
--company <co> Operator company (USER.md only; optional)
|
|
871
|
-
--working-style <s> Operator working style (USER.md only; optional)
|
|
1121
|
+
--name <name> Operator name, max 48 chars (fills USER.md + AGENTS.md §1)
|
|
1122
|
+
--role <role> Operator role, max 48 chars
|
|
1123
|
+
--company <co> Operator company, max 64 chars (USER.md only; optional)
|
|
1124
|
+
--working-style <s> Operator working style, max 80 chars (USER.md only; optional)
|
|
1125
|
+
--persist Install the managed command after workspace creation
|
|
1126
|
+
--no-persist Do not install or prompt to install the managed command
|
|
1127
|
+
`.trim(),
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function showLauncherHelp() {
|
|
1132
|
+
console.log(
|
|
1133
|
+
`
|
|
1134
|
+
Usage: phronesis launcher install [--force-launcher] [--no-path-offer]
|
|
1135
|
+
phronesis launcher install --backend app --app-target <absolute cli.js>
|
|
1136
|
+
--app-runtime <absolute Electron> [--app-version <asserted-version>]
|
|
1137
|
+
[--if-owned] [--force-launcher] [--no-path-offer]
|
|
1138
|
+
[--expect-absent | --expect-owner <backend> --expect-version <version>]
|
|
1139
|
+
phronesis launcher remove [--backend app|self-copy] [--force-launcher]
|
|
1140
|
+
phronesis launcher path [--append] [--append-consented-by-surface] [--json]
|
|
1141
|
+
phronesis launcher status [--json]
|
|
1142
|
+
|
|
1143
|
+
Installs or removes the managed command at ~/.phronesis/bin/phronesis. Install
|
|
1144
|
+
defaults to a self-copy generation. App mode records an installed app's bundled
|
|
1145
|
+
CLI and Electron runtime without copying either one. Both modes commit by atomically
|
|
1146
|
+
renaming launcher.env. Remove is backend-owner-checked and removes the manifest
|
|
1147
|
+
before the dispatcher. Cross-backend replacement requires --force-launcher. The path
|
|
1148
|
+
command reports the exact shell-profile edit; --append prompts on a TTY and never
|
|
1149
|
+
writes off-TTY without the documented consenting-surface integration flag.
|
|
1150
|
+
|
|
1151
|
+
Options:
|
|
1152
|
+
--force-launcher Replace/remove a foreign owner, invalid or unreadable manifest, or unowned
|
|
1153
|
+
dispatcher; also permits a downgrade. Ownership by another uid is never overridden.
|
|
1154
|
+
--backend Launcher owner: app or self-copy (default: self-copy).
|
|
1155
|
+
--app-target Absolute bundled cli.js path (app installs only).
|
|
1156
|
+
--app-runtime Absolute Electron executable path (app installs only).
|
|
1157
|
+
--app-version Optional assertion; must match the target package.json version.
|
|
1158
|
+
--if-owned Re-stamp only when a valid app-owned manifest already exists; cannot be
|
|
1159
|
+
combined with the --expect-* disclosure-binding options.
|
|
1160
|
+
--expect-absent Refuse if the launcher slot changed since an absent-slot disclosure.
|
|
1161
|
+
--expect-owner Expected prior app|self-copy owner; requires --expect-version.
|
|
1162
|
+
--expect-version Expected prior version; requires --expect-owner.
|
|
1163
|
+
--no-path-offer Commit the launcher without the terminal PATH offer.
|
|
1164
|
+
--append On a TTY, disclose and confirm before appending; off-TTY, print only.
|
|
1165
|
+
--append-consented-by-surface
|
|
1166
|
+
Off-TTY only: append after the calling surface has shown the exact file
|
|
1167
|
+
and line and received operator confirmation, then print both as a record.
|
|
1168
|
+
On a TTY this still discloses and prompts. Integration surfaces only;
|
|
1169
|
+
scripts must not pass this flag.
|
|
1170
|
+
--json Print launcher path/status data as JSON.
|
|
1171
|
+
`.trim(),
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function showProfileHelp() {
|
|
1176
|
+
console.log(
|
|
1177
|
+
`
|
|
1178
|
+
Usage: phronesis profile set [--name <name>] [--role <role>]
|
|
1179
|
+
[--company <co>] [--working-style <style>] [--domain <slug>] [--dir <install>]
|
|
1180
|
+
|
|
1181
|
+
Updates the stamped operator profile in place — the non-destructive repair verb for an
|
|
1182
|
+
install that still carries [Name]/[Role] placeholders (a scripted init leaves them) or a
|
|
1183
|
+
profile that has changed. Touches only the stamped field lines in USER.md and the always-loaded
|
|
1184
|
+
AGENTS.md §1 operator line; never rewrites surrounding operator prose, and never needs --force.
|
|
1185
|
+
If a field line cannot be located uniquely, it fails closed and points you at the file to edit.
|
|
1186
|
+
|
|
1187
|
+
Options:
|
|
1188
|
+
--name <name> Operator name, max 48 chars (updates USER.md + AGENTS.md §1)
|
|
1189
|
+
--role <role> Operator role, max 48 chars
|
|
1190
|
+
--company <co> Operator company, max 64 chars (USER.md only)
|
|
1191
|
+
--working-style <s> Operator working style, max 80 chars (USER.md only)
|
|
1192
|
+
--domain <slug> Domain whose profile to update (default: the sole registered domain)
|
|
1193
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
872
1194
|
`.trim(),
|
|
873
1195
|
);
|
|
874
1196
|
}
|
|
@@ -965,12 +1287,17 @@ function showActHelp() {
|
|
|
965
1287
|
console.log(
|
|
966
1288
|
`
|
|
967
1289
|
Usage: phronesis act <type> --json '<payload>' --session <id> [--domain <slug>] [--actor <a>] [--surface <s>] [--invocation-id <id>] [--dir <install>]
|
|
1290
|
+
phronesis act <type> --schema [--dir <install>]
|
|
968
1291
|
|
|
969
1292
|
The action-type execution floor (specs/ontology.md §"Action type contract") — plumbing,
|
|
970
1293
|
not porcelain: skills compose it; it is not a promoted operator verb. One shared core
|
|
971
1294
|
runs every action: schema validation, permission gate, action_type.requested/committed
|
|
972
1295
|
events, and a staged atomic write that must pass the ontology contract before it lands.
|
|
973
1296
|
|
|
1297
|
+
Use --schema to PRINT a type's effective payload schema (fields, kinds, required markers,
|
|
1298
|
+
and enum values) instead of running it — a read-only introspection of the same registry
|
|
1299
|
+
table --json is validated against. --schema and --json are mutually exclusive.
|
|
1300
|
+
|
|
974
1301
|
Registered types: log_decision, enrich_person, update_project_state,
|
|
975
1302
|
track_commitment, record_outcome, promote_to_codex (handlers: core-0009/0010),
|
|
976
1303
|
compile (the agentic quality-gate writer for insight + organization, core-0017 — review
|
|
@@ -983,13 +1310,15 @@ table (operator overrides in .phronesis/registry.json action_types) is shown by
|
|
|
983
1310
|
validated. Execution context never goes here.
|
|
984
1311
|
--session <id> Session the action belongs to (required).
|
|
985
1312
|
--domain <slug> Domain workspace (default: the domains/<slug>/ the cwd is inside,
|
|
986
|
-
else
|
|
1313
|
+
else the sole registered domain; several registered → pass one).
|
|
987
1314
|
--actor <a> Defaults to $PHRONESIS_ACTOR, else 'agent'.
|
|
988
1315
|
--surface <s> Defaults to 'cli'.
|
|
989
1316
|
--invocation-id <i> Attribution provenance for a surrounding skill invocation. It is
|
|
990
1317
|
caller-controlled (not an authority); adapters and the eval harness
|
|
991
1318
|
may supply it. PHRONESIS_INVOCATION_ID is the equivalent env input.
|
|
992
1319
|
--dir <install> Installation root (default: walk up to .phronesis/registry.json).
|
|
1320
|
+
--schema Print <type>'s effective payload schema and exit (read-only; no
|
|
1321
|
+
write, no event). Mutually exclusive with --json.
|
|
993
1322
|
|
|
994
1323
|
Approval-gated actions refuse without an approval signal: the env var
|
|
995
1324
|
PHRONESIS_APPROVAL_SOURCE (e.g. operator:interactive), set by an approving adapter and
|
|
@@ -1039,7 +1368,7 @@ function showCompileHelp() {
|
|
|
1039
1368
|
Usage:
|
|
1040
1369
|
phronesis compile accept <draft_path> --session <id> [--dir <install>] [--surface <s>]
|
|
1041
1370
|
phronesis compile reject <draft_path> --session <id> [--reason '<why>'] [--dir <install>] [--surface <s>]
|
|
1042
|
-
phronesis compile review --session <id> [--domain <slug>] [--approve-all --rationale '<why>'] [--resume] [--json]
|
|
1371
|
+
phronesis compile review --session <id> [--domain <slug>] [--approve-all --rationale '<why>'] [--approve-only '<p1,p2>'] [--resume] [--json]
|
|
1043
1372
|
|
|
1044
1373
|
The operator review primitives over a compile candidate (specs/security.md draft
|
|
1045
1374
|
lifecycle; core-0017 H9). An untrusted compile stages a draft under .phronesis/drafts/ and
|
|
@@ -1069,8 +1398,11 @@ review: The batch-approval UX over the same per-draft accept move (T7.5). Lists
|
|
|
1069
1398
|
<draft_path> (accept/reject) The .phronesis/drafts/{domain}/{type-dir}/{name}.md candidate.
|
|
1070
1399
|
--session <id> Review session the events belong to (required).
|
|
1071
1400
|
--reason '<why>' (reject) recorded on compile.rejected.
|
|
1072
|
-
--domain <slug> (review) Domain whose drafts to review (default:
|
|
1401
|
+
--domain <slug> (review) Domain whose drafts to review (default: the sole registered domain; several registered → pass one).
|
|
1073
1402
|
--approve-all (review) Promote the provably-clean set as one batch.
|
|
1403
|
+
--approve-only '<p1,p2>' (review) Restrict --approve-all to a caller-reviewed SUBSET (drafts the
|
|
1404
|
+
operator confirmed); an eligible-but-unreviewed draft is not promoted. Absent →
|
|
1405
|
+
all eligible. A subset filter only — digest/suspicious eligibility is unchanged.
|
|
1074
1406
|
--rationale '<why>'(review) Required with --approve-all; recorded on every batch compile.accepted.
|
|
1075
1407
|
--resume (review) Reconcile a crashed batch from the durable journal.
|
|
1076
1408
|
--json (review) Emit the plan / result as JSON.
|
|
@@ -1164,6 +1496,8 @@ a repo root (skills/ + changes/ present), never an install.
|
|
|
1164
1496
|
--rebaseline Reset the comparison basis after evaluation-identity drift: re-seed
|
|
1165
1497
|
the baseline under the new identity (absolute floor on both axes,
|
|
1166
1498
|
no regression check) and write a rebaseline record
|
|
1499
|
+
--verify-current Certify the already-deployed --to pin and seed/refresh PINS.md
|
|
1500
|
+
without changing the skill version or prompt_pin
|
|
1167
1501
|
--coverage-floor <f> Min per-run warranted-capture hit-rate (default: 1.0)
|
|
1168
1502
|
--quality-floor <f> Min per-criterion score across k runs (default: 4)
|
|
1169
1503
|
--rationale <text> Recorded on an override / rebaseline record
|
|
@@ -1220,6 +1554,8 @@ function showHooksHelp() {
|
|
|
1220
1554
|
console.log(
|
|
1221
1555
|
`
|
|
1222
1556
|
Usage:
|
|
1557
|
+
phronesis hooks refresh [--dir <install>] [--apply <path> ... | --apply-all]
|
|
1558
|
+
[--persist | --no-persist]
|
|
1223
1559
|
phronesis hooks due-check --session <id> [--actor <a>] [--surface <s>] [--dir <install>]
|
|
1224
1560
|
phronesis hooks sweep-status --session <id> [--json] [--dir <install>]
|
|
1225
1561
|
phronesis hooks skill-start --payload '<hook-json>' [--dir <install>]
|
|
@@ -1227,6 +1563,20 @@ Usage:
|
|
|
1227
1563
|
phronesis hooks skill-abandon --payload '<hook-json>' [--json] [--dir <install>]
|
|
1228
1564
|
phronesis hooks skill-sweep-stale --session <id> [--json] [--dir <install>]
|
|
1229
1565
|
|
|
1566
|
+
refresh: Reports every init-derived hook-manifest path as current / differs /
|
|
1567
|
+
missing, plus unknown siblings as extra-unknown (never touched). The
|
|
1568
|
+
default is report-only off-TTY. Writes require per-path --apply or
|
|
1569
|
+
--apply-all; blanket approval covers only safely attributable stamped
|
|
1570
|
+
updates. Unstamped or operator-modified paths require targeted per-path
|
|
1571
|
+
approval. Restamps re-check reviewed bytes at the final atomic/no-follow
|
|
1572
|
+
write boundary and update only .phronesis/hook-stamp.json, never
|
|
1573
|
+
the broader upgrade baseline. .claude/settings.json receives a bounded
|
|
1574
|
+
Phronesis-hook-entry merge that preserves operator settings and hooks.
|
|
1575
|
+
If the launcher is absent or self-copy-owned but stale, --persist
|
|
1576
|
+
installs/repairs it off-TTY; --no-persist suppresses the TTY offer.
|
|
1577
|
+
App-owned or ownership-unreadable launchers are reported and never
|
|
1578
|
+
force-replaced by this hook migration.
|
|
1579
|
+
|
|
1230
1580
|
Session-boundary hook support (specs/hooks.md §"Emission discipline"; change 0010 D4).
|
|
1231
1581
|
The scaffolded Claude Code hooks (.phronesis/hooks/session-start.sh, session-sweep.sh,
|
|
1232
1582
|
skill-lifecycle.sh) shell out to these; any Tier-1 surface adapter can do the same.
|
|
@@ -1254,18 +1604,50 @@ skill-start / skill-complete / skill-abandon / skill-sweep-stale:
|
|
|
1254
1604
|
);
|
|
1255
1605
|
}
|
|
1256
1606
|
|
|
1607
|
+
function showHookHelp() {
|
|
1608
|
+
console.log(
|
|
1609
|
+
`
|
|
1610
|
+
Usage: phronesis hook <event> --surface <surface> [--mode <start|abandon>]
|
|
1611
|
+
|
|
1612
|
+
The resolved executable interface used by emitted surface hooks (change 0065). It reads
|
|
1613
|
+
the surface event JSON from stdin, runs the deterministic hook logic in this CLI process,
|
|
1614
|
+
and emits the surface's required stdout shape. Hook scripts resolve one managed-first
|
|
1615
|
+
executable and contain no JSON parser or nested runtime invocation.
|
|
1616
|
+
|
|
1617
|
+
Events:
|
|
1618
|
+
recall-guard
|
|
1619
|
+
session-start
|
|
1620
|
+
session-sweep
|
|
1621
|
+
session-sweep-subagent
|
|
1622
|
+
session-sweep-precompact
|
|
1623
|
+
skill-lifecycle (--mode start|abandon)
|
|
1624
|
+
compile-active-context
|
|
1625
|
+
|
|
1626
|
+
Options:
|
|
1627
|
+
--surface <surface> Surface attribution/output contract (claude-code or codex)
|
|
1628
|
+
--mode <mode> skill-lifecycle variant: start or abandon
|
|
1629
|
+
`.trim(),
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1257
1633
|
function showAdapterHelp() {
|
|
1258
1634
|
console.log(
|
|
1259
1635
|
`
|
|
1260
1636
|
Usage: phronesis adapter test <surface> [--dir <install>] [--domain <slug>] [--json]
|
|
1261
1637
|
|
|
1262
1638
|
The per-surface adapter self-check (specs/tech-stack.md §"Adapter test command";
|
|
1263
|
-
specs/event-log.md §"Adapter test"). V1 ships the Codex adapter
|
|
1639
|
+
specs/event-log.md §"Adapter test"). V1 ships the Codex and Craft Phronesis Desktop adapter tests.
|
|
1640
|
+
|
|
1641
|
+
adapter test desktop (change 0055) — auto-asserts the first-party in-process reinforcement +
|
|
1642
|
+
lifecycle CONTRACT via @phronesis/core against a throwaway synthetic install: three guard fixtures
|
|
1643
|
+
(seeded-state injection / no-cue / honest-negative), due-check surfacing, sweep block-once, and no
|
|
1644
|
+
fabricated skill.invoked/completed on the error path. The live app UI half stays operator-verified.
|
|
1264
1645
|
|
|
1265
1646
|
adapter test codex — asserts the deterministic mechanics of the project-local Codex
|
|
1266
1647
|
wiring (change 0028 / core-0034): domains/<slug>/.codex/hooks.json wires each native
|
|
1267
|
-
event to its bare-path launcher, the launchers resolve the install root and
|
|
1268
|
-
|
|
1648
|
+
event to its bare-path launcher, the launchers resolve the install root and bind stdin
|
|
1649
|
+
directly to phronesis hook <event> --surface codex, and an unswept Stop yields
|
|
1650
|
+
{"decision":"block"}.
|
|
1269
1651
|
It reports the compliance LEVEL — Level 1 (reinforcement: recall / due-check / sweep) and
|
|
1270
1652
|
Level 2 (skill-lifecycle), which is PARTIAL on Codex (no UserPromptExpansion/StopFailure;
|
|
1271
1653
|
no fabricated skill.invoked/completed) — and points at the operator-verified desktop-app
|
|
@@ -1274,7 +1656,10 @@ It is read-only: no canonical state is written and no events are emitted.
|
|
|
1274
1656
|
|
|
1275
1657
|
Open Codex AT the domain workspace dir (Codex loads .codex/ only from the opened
|
|
1276
1658
|
directory, no parent-walk) and trust the project hooks (CLI /hooks, or the app's
|
|
1277
|
-
Settings → Coding → Hooks).
|
|
1659
|
+
Settings → Coding → Hooks). Empirically verified 2026-07-18 on Codex
|
|
1660
|
+
v0.145.0-alpha.18, per-hook trust keys on the hooks.json registration, not script
|
|
1661
|
+
bytes: launcher/_resolve.sh edits did not invalidate trust; a changed config should
|
|
1662
|
+
show the review banner. This is version-scoped and may change in later Codex versions.
|
|
1278
1663
|
|
|
1279
1664
|
Options:
|
|
1280
1665
|
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
@@ -1326,7 +1711,7 @@ Arguments:
|
|
|
1326
1711
|
query The search text (quote it)
|
|
1327
1712
|
|
|
1328
1713
|
Options:
|
|
1329
|
-
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else
|
|
1714
|
+
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else the sole registered domain)
|
|
1330
1715
|
--all-domains Search every domain (default scope is one domain + codex)
|
|
1331
1716
|
--include-artifacts Explicitly include durable artifacts for this search (not indexed by default)
|
|
1332
1717
|
--limit N Max results (default: 10)
|
|
@@ -1347,7 +1732,7 @@ decisions, the people involved, and the raw sources they cite (per-result "sourc
|
|
|
1347
1732
|
Semantic rerank (Stage 3) is V1.5+/BYO; this surface stays the same.
|
|
1348
1733
|
|
|
1349
1734
|
Options:
|
|
1350
|
-
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else
|
|
1735
|
+
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else the sole registered domain)
|
|
1351
1736
|
--all-domains Query every domain (default scope is one domain + codex)
|
|
1352
1737
|
--include-artifacts Explicitly include durable artifacts for this query (not indexed by default)
|
|
1353
1738
|
--limit N Max seed results (default: 10)
|
|
@@ -1372,7 +1757,7 @@ It writes nothing: no compiled edits, no raw/degraded captures, no events, no dr
|
|
|
1372
1757
|
and no machine-index repair.
|
|
1373
1758
|
|
|
1374
1759
|
Options:
|
|
1375
|
-
--domain <slug> Domain scope (default: the active domain, else
|
|
1760
|
+
--domain <slug> Domain scope (default: the active domain, else the sole registered domain)
|
|
1376
1761
|
--all-domains Lint every registered domain
|
|
1377
1762
|
--stale-months N Staleness threshold for forward refs and seed adoption (default: 6)
|
|
1378
1763
|
--json Emit the machine-readable lint envelope
|
|
@@ -1388,15 +1773,16 @@ Usage: phronesis doctor [--dir <install>] [--json]
|
|
|
1388
1773
|
|
|
1389
1774
|
Read-only install-health overview — the dogfood "is my install wired correctly?"
|
|
1390
1775
|
command. Composes existing diagnostics into a one-screen OK / warn / fail report:
|
|
1391
|
-
install root, registered domain(s), skill registry,
|
|
1392
|
-
status, USER.md placeholder check, and pending-draft count.
|
|
1776
|
+
install root, managed launcher/manifest integrity, registered domain(s), skill registry,
|
|
1777
|
+
hook wiring, Codex surface-adapter status, USER.md placeholder check, and pending-draft count.
|
|
1393
1778
|
|
|
1394
1779
|
It writes nothing to the install and emits no events (the install is byte-identical
|
|
1395
1780
|
after a run). It reports; it does not fix.
|
|
1396
1781
|
|
|
1397
1782
|
Exit is non-zero only on a FAIL check (no/invalid/unreadable install, an invalid skill
|
|
1398
|
-
registry). WARN checks (
|
|
1399
|
-
placeholders, pending drafts) are informational
|
|
1783
|
+
registry). WARN checks (missing or non-compliant launcher state, incomplete hook wiring,
|
|
1784
|
+
a non-compliant Codex adapter, USER.md placeholders, pending drafts) are informational
|
|
1785
|
+
and never fail the command.
|
|
1400
1786
|
|
|
1401
1787
|
Options:
|
|
1402
1788
|
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
@@ -1490,6 +1876,12 @@ async function run(argv) {
|
|
|
1490
1876
|
case 'init-help':
|
|
1491
1877
|
showInitHelp();
|
|
1492
1878
|
break;
|
|
1879
|
+
case 'launcher-help':
|
|
1880
|
+
showLauncherHelp();
|
|
1881
|
+
break;
|
|
1882
|
+
case 'profile-help':
|
|
1883
|
+
showProfileHelp();
|
|
1884
|
+
break;
|
|
1493
1885
|
case 'export-help':
|
|
1494
1886
|
showExportHelp();
|
|
1495
1887
|
break;
|
|
@@ -1529,6 +1921,9 @@ async function run(argv) {
|
|
|
1529
1921
|
case 'hooks-help':
|
|
1530
1922
|
showHooksHelp();
|
|
1531
1923
|
break;
|
|
1924
|
+
case 'hook-help':
|
|
1925
|
+
showHookHelp();
|
|
1926
|
+
break;
|
|
1532
1927
|
case 'adapter-help':
|
|
1533
1928
|
showAdapterHelp();
|
|
1534
1929
|
break;
|
|
@@ -1566,6 +1961,78 @@ async function run(argv) {
|
|
|
1566
1961
|
role: parsed.role,
|
|
1567
1962
|
company: parsed.company,
|
|
1568
1963
|
workingStyle: parsed.workingStyle,
|
|
1964
|
+
persist: parsed.persist,
|
|
1965
|
+
});
|
|
1966
|
+
break;
|
|
1967
|
+
}
|
|
1968
|
+
case 'launcher-install': {
|
|
1969
|
+
const [{ runLauncherInstall }, { confirmPathAppend }] = await Promise.all([
|
|
1970
|
+
import('./launcher.js'),
|
|
1971
|
+
import('./prompts.js'),
|
|
1972
|
+
]);
|
|
1973
|
+
try {
|
|
1974
|
+
await runLauncherInstall({
|
|
1975
|
+
forceLauncher: parsed.forceLauncher,
|
|
1976
|
+
backend: parsed.backend,
|
|
1977
|
+
appTarget: parsed.appTarget,
|
|
1978
|
+
appRuntime: parsed.appRuntime,
|
|
1979
|
+
appVersion: parsed.appVersion,
|
|
1980
|
+
ifOwned: parsed.ifOwned,
|
|
1981
|
+
offerPath: parsed.offerPath,
|
|
1982
|
+
expectedPrior: parsed.expectedPrior,
|
|
1983
|
+
promptPath: confirmPathAppend,
|
|
1984
|
+
});
|
|
1985
|
+
} catch (error) {
|
|
1986
|
+
console.error(`Launcher install failed: ${error.message}`);
|
|
1987
|
+
process.exitCode = 1;
|
|
1988
|
+
}
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1991
|
+
case 'launcher-remove': {
|
|
1992
|
+
const { runLauncherRemove } = await import('./launcher.js');
|
|
1993
|
+
try {
|
|
1994
|
+
await runLauncherRemove({ forceLauncher: parsed.forceLauncher, backend: parsed.backend });
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
console.error(`Launcher remove failed: ${error.message}`);
|
|
1997
|
+
process.exitCode = 1;
|
|
1998
|
+
}
|
|
1999
|
+
break;
|
|
2000
|
+
}
|
|
2001
|
+
case 'launcher-path': {
|
|
2002
|
+
const [{ runLauncherPath }, { confirmPathAppend }] = await Promise.all([
|
|
2003
|
+
import('./launcher.js'),
|
|
2004
|
+
import('./prompts.js'),
|
|
2005
|
+
]);
|
|
2006
|
+
try {
|
|
2007
|
+
await runLauncherPath({
|
|
2008
|
+
append: parsed.append,
|
|
2009
|
+
surfaceConsented: parsed.surfaceConsented,
|
|
2010
|
+
json: parsed.json,
|
|
2011
|
+
prompt: confirmPathAppend,
|
|
2012
|
+
});
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
console.error(`Launcher PATH update failed: ${error.message}`);
|
|
2015
|
+
process.exitCode = 1;
|
|
2016
|
+
}
|
|
2017
|
+
break;
|
|
2018
|
+
}
|
|
2019
|
+
case 'launcher-status': {
|
|
2020
|
+
const { launcherStatus } = await import('./launcher.js');
|
|
2021
|
+
const status = launcherStatus();
|
|
2022
|
+
if (parsed.json) console.log(JSON.stringify(status));
|
|
2023
|
+
else if (!status.exists) console.log('Managed `phronesis` command is not installed.');
|
|
2024
|
+
else console.log(`Managed command owner=${status.owner ?? 'unrecognized'}, version=${status.version ?? 'unknown'}.`);
|
|
2025
|
+
break;
|
|
2026
|
+
}
|
|
2027
|
+
case 'profile-set': {
|
|
2028
|
+
const { runProfileSet } = await import('./profile.js');
|
|
2029
|
+
await runProfileSet({
|
|
2030
|
+
dir: parsed.dir,
|
|
2031
|
+
domain: parsed.domain,
|
|
2032
|
+
name: parsed.name,
|
|
2033
|
+
role: parsed.role,
|
|
2034
|
+
company: parsed.company,
|
|
2035
|
+
workingStyle: parsed.workingStyle,
|
|
1569
2036
|
});
|
|
1570
2037
|
break;
|
|
1571
2038
|
}
|
|
@@ -1637,6 +2104,11 @@ async function run(argv) {
|
|
|
1637
2104
|
});
|
|
1638
2105
|
break;
|
|
1639
2106
|
}
|
|
2107
|
+
case 'act-schema': {
|
|
2108
|
+
const { runActSchema } = await import('./act.js');
|
|
2109
|
+
await runActSchema({ type: parsed.type, dir: parsed.dir });
|
|
2110
|
+
break;
|
|
2111
|
+
}
|
|
1640
2112
|
case 'ingest': {
|
|
1641
2113
|
const { runIngest } = await import('./ingest.js');
|
|
1642
2114
|
await runIngest({
|
|
@@ -1662,6 +2134,7 @@ async function run(argv) {
|
|
|
1662
2134
|
session: parsed.session,
|
|
1663
2135
|
surface: parsed.surface,
|
|
1664
2136
|
dir: parsed.dir,
|
|
2137
|
+
reviewedDigest: parsed.reviewedDigest,
|
|
1665
2138
|
});
|
|
1666
2139
|
break;
|
|
1667
2140
|
}
|
|
@@ -1685,6 +2158,7 @@ async function run(argv) {
|
|
|
1685
2158
|
dir: parsed.dir,
|
|
1686
2159
|
rationale: parsed.rationale,
|
|
1687
2160
|
approveAll: parsed.approveAll,
|
|
2161
|
+
approveOnly: parsed.approveOnly,
|
|
1688
2162
|
resume: parsed.resume,
|
|
1689
2163
|
json: parsed.json,
|
|
1690
2164
|
});
|
|
@@ -1727,6 +2201,7 @@ async function run(argv) {
|
|
|
1727
2201
|
k: parsed.k,
|
|
1728
2202
|
acceptRegression: parsed.acceptRegression,
|
|
1729
2203
|
rebaseline: parsed.rebaseline,
|
|
2204
|
+
verifyCurrent: parsed.verifyCurrent,
|
|
1730
2205
|
shapeChange: parsed.shapeChange,
|
|
1731
2206
|
qualityFloor: parsed.qualityFloor,
|
|
1732
2207
|
coverageFloor: parsed.coverageFloor,
|
|
@@ -1749,6 +2224,21 @@ async function run(argv) {
|
|
|
1749
2224
|
});
|
|
1750
2225
|
break;
|
|
1751
2226
|
}
|
|
2227
|
+
case 'hooks-refresh': {
|
|
2228
|
+
const { runHooksRefresh } = await import('./hooks-refresh.js');
|
|
2229
|
+
try {
|
|
2230
|
+
await runHooksRefresh({
|
|
2231
|
+
dir: parsed.dir,
|
|
2232
|
+
apply: parsed.apply,
|
|
2233
|
+
applyAll: parsed.applyAll,
|
|
2234
|
+
persist: parsed.persist,
|
|
2235
|
+
});
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
console.error(`hooks refresh failed: ${error.message}`);
|
|
2238
|
+
process.exitCode = 1;
|
|
2239
|
+
}
|
|
2240
|
+
break;
|
|
2241
|
+
}
|
|
1752
2242
|
case 'hooks-sweep-status': {
|
|
1753
2243
|
const { runHooksSweepStatus } = await import('./hooks.js');
|
|
1754
2244
|
await runHooksSweepStatus({ session: parsed.session, dir: parsed.dir, json: parsed.json });
|
|
@@ -1780,6 +2270,11 @@ async function run(argv) {
|
|
|
1780
2270
|
await runHooksSkillSweepStale({ session: parsed.session, dir: parsed.dir, json: parsed.json });
|
|
1781
2271
|
break;
|
|
1782
2272
|
}
|
|
2273
|
+
case 'hook': {
|
|
2274
|
+
const { runHook } = await import('./hook.js');
|
|
2275
|
+
await runHook({ event: parsed.event, surface: parsed.surface, mode: parsed.mode });
|
|
2276
|
+
break;
|
|
2277
|
+
}
|
|
1783
2278
|
case 'adapter-test': {
|
|
1784
2279
|
const { runAdapterTest } = await import('./adapter.js');
|
|
1785
2280
|
await runAdapterTest({
|
|
@@ -1880,7 +2375,7 @@ async function run(argv) {
|
|
|
1880
2375
|
}
|
|
1881
2376
|
}
|
|
1882
2377
|
|
|
1883
|
-
export { parseArgs, run, showHelp, showInitHelp };
|
|
2378
|
+
export { parseArgs, run, showHelp, showInitHelp, showLauncherHelp, showHooksHelp };
|
|
1884
2379
|
|
|
1885
2380
|
if (process.argv[1]) {
|
|
1886
2381
|
try {
|