phronesis 1.0.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/README.md +66 -0
- package/package.json +34 -0
- package/src/act.js +61 -0
- package/src/active-context.js +56 -0
- package/src/adapter.js +386 -0
- package/src/cli.js +1893 -0
- package/src/codex-hooks-schema.js +92 -0
- package/src/compile.js +513 -0
- package/src/doctor.js +303 -0
- package/src/due.js +271 -0
- package/src/eval.js +467 -0
- package/src/event.js +212 -0
- package/src/export.js +136 -0
- package/src/guard.js +89 -0
- package/src/hooks.js +230 -0
- package/src/ingest.js +965 -0
- package/src/init.js +150 -0
- package/src/layout.js +394 -0
- package/src/links.js +83 -0
- package/src/lint.js +58 -0
- package/src/profile.js +116 -0
- package/src/prompts.js +26 -0
- package/src/registry.js +190 -0
- package/src/scaffold.js +134 -0
- package/src/search.js +139 -0
- package/src/skill-discovery.js +117 -0
- package/src/skills-bump.js +885 -0
- package/src/skills.js +355 -0
- package/src/subscription.js +5 -0
- package/src/validate.js +152 -0
- package/templates/claude/settings.json +86 -0
- package/templates/codex/INDEX.md +68 -0
- package/templates/codex/seed/ai-practice/compile-dont-just-capture.md +42 -0
- package/templates/codex/seed/ai-practice/design-for-the-distribution.md +42 -0
- package/templates/codex/seed/ai-practice/from-spec-author-to-outcome-curator.md +43 -0
- package/templates/codex/seed/ai-practice/recall-builds-trust-when-calibrated.md +70 -0
- package/templates/codex/seed/ai-practice/the-eval-is-the-spec.md +42 -0
- package/templates/codex/seed/ai-practice/the-gold-dust-leaves-no-trace.md +72 -0
- package/templates/codex/seed/ai-practice/the-model-is-not-the-moat.md +40 -0
- package/templates/codex/seed/ai-practice/when-generation-is-cheap-judgment-is-the-work.md +39 -0
- package/templates/codex/seed/judgment/contextual-stewardship-is-the-senior-skill.md +50 -0
- package/templates/codex/seed/judgment/decision-quality-is-distinct-from-outcome-quality.md +39 -0
- package/templates/codex/seed/judgment/embrace-reality-ego-and-blind-spots.md +38 -0
- package/templates/codex/seed/judgment/groups-seek-consensus-individuals-seek-truth.md +44 -0
- package/templates/codex/seed/judgment/heresies-are-where-the-edge-lives.md +42 -0
- package/templates/codex/seed/judgment/judgment-compounds-through-honest-reflection.md +38 -0
- package/templates/codex/seed/judgment/phronesis-is-judgment-in-the-particular.md +67 -0
- package/templates/codex/seed/judgment/show-me-the-apparatus.md +40 -0
- package/templates/codex/seed/judgment/small-judgment-gaps-compound-under-leverage.md +37 -0
- package/templates/codex/seed/judgment/taste-is-compressed-judgment.md +38 -0
- package/templates/codex/seed/judgment/trust-taste-only-where-feedback-is-valid.md +42 -0
- package/templates/codex/seed/judgment/weigh-the-reasoning-not-the-recommendation.md +43 -0
- package/templates/codex/seed/leadership/a-managers-output-is-the-teams-output.md +35 -0
- package/templates/codex/seed/leadership/most-team-problems-are-structural.md +40 -0
- package/templates/codex/seed/leadership/next-play.md +36 -0
- package/templates/codex/seed/leadership/psychological-safety.md +39 -0
- package/templates/codex/seed/leadership/right-size-your-teams.md +38 -0
- package/templates/codex/seed/leadership/spend-time-on-high-leverage-activities.md +36 -0
- package/templates/codex/seed/leadership/talent-density-is-the-master-variable.md +39 -0
- package/templates/codex/seed/leadership/under-pressure-calm-is-contagious.md +36 -0
- package/templates/codex/seed/leadership/your-best-alternative-is-your-leverage.md +37 -0
- package/templates/codex/seed/pm/a-problem-well-stated-is-half-solved.md +39 -0
- package/templates/codex/seed/pm/a-real-strategy-makes-planning-trivial.md +40 -0
- package/templates/codex/seed/pm/barrels-and-ammunition.md +39 -0
- package/templates/codex/seed/pm/develop-taste-in-your-beliefs.md +38 -0
- package/templates/codex/seed/pm/hire-for-progress.md +44 -0
- package/templates/codex/seed/pm/jobs-are-stable-solutions-churn.md +39 -0
- package/templates/codex/seed/pm/level-by-the-ambiguity-someone-can-own.md +39 -0
- package/templates/codex/seed/pm/listen-before-you-prescribe.md +38 -0
- package/templates/codex/seed/pm/live-in-the-future-and-build-whats-missing.md +38 -0
- package/templates/codex/seed/pm/match-effort-to-leverage.md +37 -0
- package/templates/codex/seed/pm/not-my-fault-but-my-problem.md +40 -0
- package/templates/codex/seed/pm/pick-the-job-then-refuse-everything-outside-it.md +37 -0
- package/templates/codex/seed/pm/retention-sets-the-ceiling.md +38 -0
- package/templates/codex/seed/pm/scale-with-leaders-not-process.md +38 -0
- package/templates/codex/seed/pm/solution-discovery-over-problem-validation.md +38 -0
- package/templates/codex/seed/pm/tenfold-better-on-a-painful-problem.md +40 -0
- package/templates/codex-surface/README.md +73 -0
- package/templates/codex-surface/hooks/_resolve.sh +36 -0
- package/templates/codex-surface/hooks/recall-guard.sh +30 -0
- package/templates/codex-surface/hooks/session-start.sh +18 -0
- package/templates/codex-surface/hooks/session-sweep-precompact.sh +20 -0
- package/templates/codex-surface/hooks/session-sweep-subagent.sh +23 -0
- package/templates/codex-surface/hooks/session-sweep.sh +19 -0
- package/templates/codex-surface/hooks.json +69 -0
- package/templates/domains/pm/AGENTS.md +111 -0
- package/templates/domains/pm/MEMORY.md +7 -0
- package/templates/domains/pm/USER.md +8 -0
- package/templates/phronesis-hooks/compile-active-context.sh +38 -0
- package/templates/phronesis-hooks/recall-guard.sh +103 -0
- package/templates/phronesis-hooks/session-start.sh +90 -0
- package/templates/phronesis-hooks/session-sweep.sh +140 -0
- package/templates/phronesis-hooks/skill-lifecycle.sh +37 -0
- package/templates/skills/compile/SKILL.md +279 -0
- package/templates/skills/compile/evals/rubric.md +75 -0
- package/templates/skills/decision-log/SKILL.md +156 -0
- package/templates/skills/decision-log/evals/rubric.md +97 -0
- package/templates/skills/extract-gold-dust/SKILL.md +311 -0
- package/templates/skills/extract-gold-dust/evals/rubric.md +52 -0
- package/templates/skills/ingest/SKILL.md +204 -0
- package/templates/skills/ingest/evals/rubric.md +117 -0
- package/templates/skills/lint/SKILL.md +246 -0
- package/templates/skills/lint/evals/rubric.md +139 -0
- package/templates/skills/meeting-prep/SKILL.md +195 -0
- package/templates/skills/meeting-prep/evals/rubric.md +93 -0
- package/templates/skills/onboard/SKILL.md +258 -0
- package/templates/skills/onboard/evals/rubric.md +60 -0
- package/templates/skills/prd-draft/SKILL.md +193 -0
- package/templates/skills/prd-draft/evals/rubric.md +38 -0
- package/templates/skills/prep-extraction/SKILL.md +217 -0
- package/templates/skills/prep-extraction/evals/rubric.md +45 -0
- package/templates/skills/recall/SKILL.md +155 -0
- package/templates/skills/recall/evals/rubric.md +37 -0
- package/templates/skills/research/SKILL.md +247 -0
- package/templates/skills/research/evals/rubric.md +139 -0
- package/templates/skills/stakeholder-update/SKILL.md +163 -0
- package/templates/skills/stakeholder-update/evals/rubric.md +111 -0
- package/vendor/core/package.json +25 -0
- package/vendor/core/src/action-registry.js +459 -0
- package/vendor/core/src/action-writers.js +201 -0
- package/vendor/core/src/actions.js +2140 -0
- package/vendor/core/src/active-context.js +167 -0
- package/vendor/core/src/artifacts.js +301 -0
- package/vendor/core/src/content-digest.js +21 -0
- package/vendor/core/src/contract.js +245 -0
- package/vendor/core/src/event-log.js +338 -0
- package/vendor/core/src/guard.js +398 -0
- package/vendor/core/src/hook-executor.js +527 -0
- package/vendor/core/src/index.js +180 -0
- package/vendor/core/src/ingest.js +286 -0
- package/vendor/core/src/install-root.js +42 -0
- package/vendor/core/src/layout.js +13 -0
- package/vendor/core/src/link-index.js +198 -0
- package/vendor/core/src/lint.js +368 -0
- package/vendor/core/src/schedule.js +535 -0
- package/vendor/core/src/search.js +534 -0
- package/vendor/core/src/session-hooks.js +129 -0
- package/vendor/core/src/skill-lifecycle.js +479 -0
- package/vendor/core/src/skill-registry.js +524 -0
- package/vendor/core/src/subscription.js +84 -0
- package/vendor/core/src/validate-object.js +62 -0
- package/vendor/core/src/workspace-scan.js +125 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,1893 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { realpathSync } from 'node:fs';
|
|
7
|
+
import { DEFAULT_DOMAIN } from './layout.js';
|
|
8
|
+
import { COVERAGE_FLOOR_RANGE, MAX_K, QUALITY_FLOOR_RANGE } from './skills-bump.js';
|
|
9
|
+
|
|
10
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
11
|
+
if (nodeMajor < 18) {
|
|
12
|
+
console.error(`phronesis requires Node.js 18 or later. You're running ${process.versions.node}.`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const { version } = require('../package.json');
|
|
18
|
+
|
|
19
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
20
|
+
const [cmd, ...rest] = argv;
|
|
21
|
+
if (!cmd || cmd === '--help' || cmd === '-h') return { command: 'help' };
|
|
22
|
+
if (cmd === '--version' || cmd === '-v') return { command: 'version' };
|
|
23
|
+
if (cmd === 'init') return parseInitArgs(rest);
|
|
24
|
+
if (cmd === 'export') return parseExportArgs(rest);
|
|
25
|
+
if (cmd === 'validate') return parseValidateArgs(rest);
|
|
26
|
+
if (cmd === 'links') return parseLinksArgs(rest);
|
|
27
|
+
if (cmd === 'event') return parseEventArgs(rest);
|
|
28
|
+
if (cmd === 'events') return parseEventsArgs(rest);
|
|
29
|
+
if (cmd === 'act') return parseActArgs(rest);
|
|
30
|
+
if (cmd === 'ingest') return parseIngestArgs(rest);
|
|
31
|
+
if (cmd === 'compile') return parseCompileArgs(rest);
|
|
32
|
+
if (cmd === 'active-context') return parseActiveContextArgs(rest);
|
|
33
|
+
if (cmd === 'registry') return parseRegistryArgs(rest);
|
|
34
|
+
if (cmd === 'skills') return parseSkillsArgs(rest);
|
|
35
|
+
if (cmd === 'hooks') return parseHooksArgs(rest);
|
|
36
|
+
if (cmd === 'adapter') return parseAdapterArgs(rest);
|
|
37
|
+
if (cmd === 'guard') return parseGuardArgs(rest);
|
|
38
|
+
if (cmd === 'search') return parseRetrievalArgs(rest, 'search');
|
|
39
|
+
if (cmd === 'query') return parseRetrievalArgs(rest, 'query');
|
|
40
|
+
if (cmd === 'lint') return parseLintArgs(rest);
|
|
41
|
+
if (cmd === 'doctor') return parseDoctorArgs(rest);
|
|
42
|
+
if (cmd === 'due') return parseDueArgs(rest);
|
|
43
|
+
if (cmd === 'eval') return parseEvalArgs(rest);
|
|
44
|
+
if (cmd === 'index') return parseIndexArgs(rest);
|
|
45
|
+
return { command: 'unknown', input: cmd };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseInitArgs(args) {
|
|
49
|
+
let dir;
|
|
50
|
+
let force = false;
|
|
51
|
+
// domain + the minimal-profile value flags (core-0039). Each accepts `--flag value` and `--flag=value`.
|
|
52
|
+
const VALUE_FLAGS = ['domain', 'name', 'role', 'company', 'working-style'];
|
|
53
|
+
const values = {};
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
56
|
+
const a = args[i];
|
|
57
|
+
if (a === '--help' || a === '-h') return { command: 'init-help' };
|
|
58
|
+
if (a === '--force' || a === '-f') {
|
|
59
|
+
force = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
let matched = false;
|
|
63
|
+
for (const f of VALUE_FLAGS) {
|
|
64
|
+
if (a === `--${f}`) {
|
|
65
|
+
const v = args[i + 1];
|
|
66
|
+
if (v === undefined || v.startsWith('--')) return { command: 'invalid', error: `Missing value for --${f}` };
|
|
67
|
+
values[f] = v;
|
|
68
|
+
i += 1;
|
|
69
|
+
matched = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (a.startsWith(`--${f}=`)) {
|
|
73
|
+
values[f] = a.slice(`--${f}=`.length);
|
|
74
|
+
matched = true;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (matched) continue;
|
|
79
|
+
if (a.startsWith('-')) {
|
|
80
|
+
return { command: 'invalid', error: `Unknown option for init: ${a}` };
|
|
81
|
+
}
|
|
82
|
+
if (dir) return { command: 'invalid', error: 'init accepts at most one <dir> argument.' };
|
|
83
|
+
dir = a;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
command: 'init',
|
|
88
|
+
dir,
|
|
89
|
+
force,
|
|
90
|
+
domain: values.domain,
|
|
91
|
+
name: values.name,
|
|
92
|
+
role: values.role,
|
|
93
|
+
company: values.company,
|
|
94
|
+
workingStyle: values['working-style'],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseExportArgs(args) {
|
|
99
|
+
let dir;
|
|
100
|
+
let out;
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
103
|
+
const a = args[i];
|
|
104
|
+
if (a === '--help' || a === '-h') return { command: 'export-help' };
|
|
105
|
+
if (a === '--out' || a === '-o') {
|
|
106
|
+
out = args[i + 1];
|
|
107
|
+
i += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (a.startsWith('--out=')) {
|
|
111
|
+
out = a.slice('--out='.length);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (a.startsWith('-')) {
|
|
115
|
+
return { command: 'invalid', error: `Unknown option for export: ${a}` };
|
|
116
|
+
}
|
|
117
|
+
if (dir) return { command: 'invalid', error: 'export accepts at most one <dir> argument.' };
|
|
118
|
+
dir = a;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { command: 'export', dir, out };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseValidateArgs(args) {
|
|
125
|
+
let dir;
|
|
126
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
127
|
+
const a = args[i];
|
|
128
|
+
if (a === '--help' || a === '-h') return { command: 'validate-help' };
|
|
129
|
+
if (a.startsWith('-')) {
|
|
130
|
+
return { command: 'invalid', error: `Unknown option for validate: ${a}` };
|
|
131
|
+
}
|
|
132
|
+
if (dir) return { command: 'invalid', error: 'validate accepts at most one <dir> argument.' };
|
|
133
|
+
dir = a;
|
|
134
|
+
}
|
|
135
|
+
return { command: 'validate', dir };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseLinksArgs(args) {
|
|
139
|
+
let object;
|
|
140
|
+
let dir;
|
|
141
|
+
let hops = 2;
|
|
142
|
+
let direction = 'both';
|
|
143
|
+
let json = false;
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
146
|
+
const a = args[i];
|
|
147
|
+
if (a === '--help' || a === '-h') return { command: 'links-help' };
|
|
148
|
+
if (a === '--json') {
|
|
149
|
+
json = true;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (a === '--dir') {
|
|
153
|
+
dir = args[i + 1];
|
|
154
|
+
i += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (a.startsWith('--dir=')) {
|
|
158
|
+
dir = a.slice('--dir='.length);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (a === '--hops') {
|
|
162
|
+
hops = args[i + 1];
|
|
163
|
+
i += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (a.startsWith('--hops=')) {
|
|
167
|
+
hops = a.slice('--hops='.length);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (a === '--direction') {
|
|
171
|
+
direction = args[i + 1];
|
|
172
|
+
i += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (a.startsWith('--direction=')) {
|
|
176
|
+
direction = a.slice('--direction='.length);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (a.startsWith('-')) {
|
|
180
|
+
return { command: 'invalid', error: `Unknown option for links: ${a}` };
|
|
181
|
+
}
|
|
182
|
+
if (object) return { command: 'invalid', error: 'links accepts at most one <object> argument.' };
|
|
183
|
+
object = a;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const hopsNum = Number(hops);
|
|
187
|
+
if (!Number.isInteger(hopsNum) || hopsNum < 0) {
|
|
188
|
+
return { command: 'invalid', error: `links --hops must be a non-negative integer (got "${hops}").` };
|
|
189
|
+
}
|
|
190
|
+
if (!['out', 'in', 'both'].includes(direction)) {
|
|
191
|
+
return { command: 'invalid', error: `links --direction must be out | in | both (got "${direction}").` };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { command: 'links', object, dir, hops: hopsNum, direction, json };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Compact option parser shared by the event commands (`--name value` / `--name=value` for
|
|
198
|
+
// `values`, presence flags for `flags`, the rest positional). The older parsers above predate
|
|
199
|
+
// it and stay hand-rolled; new multi-option commands use this to avoid the per-flag boilerplate.
|
|
200
|
+
function parseOptions(args, { values = [], flags = [] } = {}) {
|
|
201
|
+
const valueSet = new Set(values);
|
|
202
|
+
const flagSet = new Set(flags);
|
|
203
|
+
const out = { values: {}, flags: {}, positionals: [] };
|
|
204
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
205
|
+
const a = args[i];
|
|
206
|
+
if (a === '--help' || a === '-h') return { help: true };
|
|
207
|
+
if (a.startsWith('--')) {
|
|
208
|
+
const eq = a.indexOf('=');
|
|
209
|
+
const name = eq === -1 ? a.slice(2) : a.slice(2, eq);
|
|
210
|
+
if (flagSet.has(name)) {
|
|
211
|
+
out.flags[name] = true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (valueSet.has(name)) {
|
|
215
|
+
if (eq === -1) {
|
|
216
|
+
// A bare `--name` at the end, or one followed by another `--option`, is a missing
|
|
217
|
+
// value — not "consume the next token". Swallowing it silently mislabels the next
|
|
218
|
+
// flag as this one's value and surfaces a misleading downstream error.
|
|
219
|
+
const next = args[i + 1];
|
|
220
|
+
if (next === undefined || next.startsWith('--')) {
|
|
221
|
+
return { error: `Missing value for --${name}` };
|
|
222
|
+
}
|
|
223
|
+
out.values[name] = next;
|
|
224
|
+
i += 1;
|
|
225
|
+
} else {
|
|
226
|
+
out.values[name] = a.slice(eq + 1);
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
return { error: `Unknown option: ${a}` };
|
|
231
|
+
}
|
|
232
|
+
if (a.startsWith('-') && a !== '-') return { error: `Unknown option: ${a}` };
|
|
233
|
+
out.positionals.push(a);
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function parseEventArgs(args) {
|
|
239
|
+
const [sub, ...rest] = args;
|
|
240
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'event-help' };
|
|
241
|
+
if (sub === 'emit') {
|
|
242
|
+
const p = parseOptions(rest, { values: ['session', 'payload', 'actor', 'surface', 'dir'] });
|
|
243
|
+
if (p.help) return { command: 'event-help' };
|
|
244
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for event emit.` };
|
|
245
|
+
if (p.positionals.length > 1) {
|
|
246
|
+
return { command: 'invalid', error: 'event emit takes one <type> argument.' };
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
command: 'event-emit',
|
|
250
|
+
type: p.positionals[0],
|
|
251
|
+
session: p.values.session,
|
|
252
|
+
payload: p.values.payload,
|
|
253
|
+
actor: p.values.actor,
|
|
254
|
+
surface: p.values.surface,
|
|
255
|
+
dir: p.values.dir,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (sub === 'tail') {
|
|
259
|
+
const p = parseOptions(rest, { values: ['type', 'since', 'dir'], flags: ['follow'] });
|
|
260
|
+
if (p.help) return { command: 'event-help' };
|
|
261
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for event tail.` };
|
|
262
|
+
if (p.positionals.length) {
|
|
263
|
+
return { command: 'invalid', error: 'event tail takes no positional arguments.' };
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
command: 'event-tail',
|
|
267
|
+
type: p.values.type,
|
|
268
|
+
since: p.values.since,
|
|
269
|
+
dir: p.values.dir,
|
|
270
|
+
follow: Boolean(p.flags.follow),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return { command: 'invalid', error: `Unknown event subcommand: ${sub} (expected: emit | tail).` };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// `act` is plumbing, not porcelain (ontology.md:163): parse-only here — semantic action
|
|
277
|
+
// inputs travel exclusively inside --json (a `--title`-style flag is an unknown option
|
|
278
|
+
// and is rejected); flags carry execution context only. The approval signal is env
|
|
279
|
+
// (PHRONESIS_APPROVAL_SOURCE), set by an approving adapter — deliberately not a flag.
|
|
280
|
+
function parseActArgs(args) {
|
|
281
|
+
const p = parseOptions(args, { values: ['json', 'session', 'domain', 'actor', 'surface', 'dir', 'invocation-id'] });
|
|
282
|
+
if (p.help) return { command: 'act-help' };
|
|
283
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for act.` };
|
|
284
|
+
if (p.positionals.length > 1) {
|
|
285
|
+
return { command: 'invalid', error: 'act takes one <type> argument (semantic inputs go inside --json).' };
|
|
286
|
+
}
|
|
287
|
+
const parsed = {
|
|
288
|
+
command: 'act',
|
|
289
|
+
type: p.positionals[0],
|
|
290
|
+
json: p.values.json,
|
|
291
|
+
session: p.values.session,
|
|
292
|
+
domain: p.values.domain,
|
|
293
|
+
actor: p.values.actor,
|
|
294
|
+
surface: p.values.surface,
|
|
295
|
+
dir: p.values.dir,
|
|
296
|
+
};
|
|
297
|
+
if (p.values['invocation-id'] !== undefined) parsed.invocationId = p.values['invocation-id'];
|
|
298
|
+
return parsed;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function parseIngestArgs(args) {
|
|
302
|
+
const p = parseOptions(args, {
|
|
303
|
+
values: ['domain', 'trust', 'lane', 'dir', 'source-connector', 'source-external-id', 'source-timestamp', 'title', 'slug'],
|
|
304
|
+
flags: ['paste', 'json'],
|
|
305
|
+
});
|
|
306
|
+
if (p.help) return { command: 'ingest-help' };
|
|
307
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for ingest.` };
|
|
308
|
+
if (p.positionals.length > 1) {
|
|
309
|
+
return { command: 'invalid', error: 'ingest takes one <url|file|dir> argument, or --paste / - for stdin.' };
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
command: 'ingest',
|
|
313
|
+
source: p.positionals[0],
|
|
314
|
+
paste: Boolean(p.flags.paste),
|
|
315
|
+
domain: p.values.domain,
|
|
316
|
+
trust: p.values.trust,
|
|
317
|
+
lane: p.values.lane || 'inbox',
|
|
318
|
+
dir: p.values.dir,
|
|
319
|
+
json: Boolean(p.flags.json),
|
|
320
|
+
sourceConnector: p.values['source-connector'],
|
|
321
|
+
sourceExternalId: p.values['source-external-id'],
|
|
322
|
+
sourceTimestamp: p.values['source-timestamp'],
|
|
323
|
+
title: p.values.title,
|
|
324
|
+
slug: p.values.slug,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// `compile accept|reject <draft>` — the operator review primitives (core-0017 H9). The
|
|
329
|
+
// draft path is the one positional; execution context (session/actor/surface/dir) and the
|
|
330
|
+
// approval signal (env, like act) ride flags/env, never the payload.
|
|
331
|
+
function parseCompileArgs(args) {
|
|
332
|
+
const [sub, ...rest] = args;
|
|
333
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'compile-help' };
|
|
334
|
+
// accept/reject are operator-driven by contract — no --actor (the events are always
|
|
335
|
+
// actor: operator; the approval signal rides env PHRONESIS_APPROVAL_SOURCE, like act).
|
|
336
|
+
if (sub === 'accept') {
|
|
337
|
+
const p = parseOptions(rest, { values: ['session', 'surface', 'dir'] });
|
|
338
|
+
if (p.help) return { command: 'compile-help' };
|
|
339
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for compile accept.` };
|
|
340
|
+
if (p.positionals.length !== 1) {
|
|
341
|
+
return { command: 'invalid', error: 'compile accept takes one <draft_path> argument.' };
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
command: 'compile-accept',
|
|
345
|
+
draftPath: p.positionals[0],
|
|
346
|
+
session: p.values.session,
|
|
347
|
+
surface: p.values.surface,
|
|
348
|
+
dir: p.values.dir,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (sub === 'reject') {
|
|
352
|
+
const p = parseOptions(rest, { values: ['session', 'reason', 'surface', 'dir'] });
|
|
353
|
+
if (p.help) return { command: 'compile-help' };
|
|
354
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for compile reject.` };
|
|
355
|
+
if (p.positionals.length !== 1) {
|
|
356
|
+
return { command: 'invalid', error: 'compile reject takes one <draft_path> argument.' };
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
command: 'compile-reject',
|
|
360
|
+
draftPath: p.positionals[0],
|
|
361
|
+
reason: p.values.reason,
|
|
362
|
+
session: p.values.session,
|
|
363
|
+
surface: p.values.surface,
|
|
364
|
+
dir: p.values.dir,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (sub === 'review') {
|
|
368
|
+
const p = parseOptions(rest, {
|
|
369
|
+
values: ['domain', 'session', 'surface', 'dir', 'rationale'],
|
|
370
|
+
flags: ['approve-all', 'resume', 'json'],
|
|
371
|
+
});
|
|
372
|
+
if (p.help) return { command: 'compile-help' };
|
|
373
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for compile review.` };
|
|
374
|
+
if (p.positionals.length) {
|
|
375
|
+
return { command: 'invalid', error: 'compile review takes no positional arguments (use --domain, --approve-all, --resume).' };
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
command: 'compile-review',
|
|
379
|
+
domain: p.values.domain,
|
|
380
|
+
session: p.values.session,
|
|
381
|
+
surface: p.values.surface,
|
|
382
|
+
dir: p.values.dir,
|
|
383
|
+
rationale: p.values.rationale,
|
|
384
|
+
approveAll: Boolean(p.flags['approve-all']),
|
|
385
|
+
resume: Boolean(p.flags.resume),
|
|
386
|
+
json: Boolean(p.flags.json),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return { command: 'invalid', error: `Unknown compile subcommand: ${sub} (expected: accept | reject | review).` };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// `active-context refresh --domain <slug>` — regenerate a domain's tracked projection
|
|
393
|
+
// (core-0017 H5). The post-compile hook shells out to it; operators rarely call it directly.
|
|
394
|
+
function parseActiveContextArgs(args) {
|
|
395
|
+
const [sub, ...rest] = args;
|
|
396
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'active-context-help' };
|
|
397
|
+
if (sub === 'refresh') {
|
|
398
|
+
const p = parseOptions(rest, { values: ['domain', 'dir'], flags: ['json'] });
|
|
399
|
+
if (p.help) return { command: 'active-context-help' };
|
|
400
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for active-context refresh.` };
|
|
401
|
+
if (p.positionals.length) {
|
|
402
|
+
return { command: 'invalid', error: 'active-context refresh takes no positional arguments.' };
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
command: 'active-context-refresh',
|
|
406
|
+
domain: p.values.domain,
|
|
407
|
+
dir: p.values.dir,
|
|
408
|
+
json: Boolean(p.flags.json),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return { command: 'invalid', error: `Unknown active-context subcommand: ${sub} (expected: refresh).` };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function parseRegistryArgs(args) {
|
|
415
|
+
const [sub, ...rest] = args;
|
|
416
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'registry-help' };
|
|
417
|
+
if (sub === 'status') {
|
|
418
|
+
const p = parseOptions(rest, { values: ['dir'], flags: ['json'] });
|
|
419
|
+
if (p.help) return { command: 'registry-help' };
|
|
420
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for registry status.` };
|
|
421
|
+
if (p.positionals.length) {
|
|
422
|
+
return { command: 'invalid', error: 'registry status takes no positional arguments.' };
|
|
423
|
+
}
|
|
424
|
+
return { command: 'registry-status', dir: p.values.dir, json: Boolean(p.flags.json) };
|
|
425
|
+
}
|
|
426
|
+
return { command: 'invalid', error: `Unknown registry subcommand: ${sub} (expected: status).` };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function parseSkillsArgs(args) {
|
|
430
|
+
const [sub, ...rest] = args;
|
|
431
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'skills-help' };
|
|
432
|
+
if (sub === 'status') {
|
|
433
|
+
const p = parseOptions(rest, { values: ['dir'], flags: ['json'] });
|
|
434
|
+
if (p.help) return { command: 'skills-help' };
|
|
435
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for skills status.` };
|
|
436
|
+
if (p.positionals.length) {
|
|
437
|
+
return { command: 'invalid', error: 'skills status takes no positional arguments.' };
|
|
438
|
+
}
|
|
439
|
+
return { command: 'skills-status', dir: p.values.dir, json: Boolean(p.flags.json) };
|
|
440
|
+
}
|
|
441
|
+
if (sub === 'bump') {
|
|
442
|
+
const p = parseOptions(rest, {
|
|
443
|
+
values: ['to', 'repo', 'k', 'coverage-floor', 'quality-floor', 'rationale', 'accepted-by', 'judge-model', 'goldens-dir', 'domain'],
|
|
444
|
+
flags: ['accept-regression', 'rebaseline', 'shape-change', 'json'],
|
|
445
|
+
});
|
|
446
|
+
if (p.help) return { command: 'skills-help' };
|
|
447
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for skills bump.` };
|
|
448
|
+
if (p.positionals.length !== 1) {
|
|
449
|
+
return { command: 'invalid', error: 'skills bump takes exactly one skill name: phronesis skills bump <skill> --to <pin>.' };
|
|
450
|
+
}
|
|
451
|
+
if (!p.values.to) {
|
|
452
|
+
return { command: 'invalid', error: 'skills bump requires --to <model_id>@<date> (the proposed prompt_pin).' };
|
|
453
|
+
}
|
|
454
|
+
let k;
|
|
455
|
+
if (p.values.k !== undefined) {
|
|
456
|
+
k = Number(p.values.k);
|
|
457
|
+
if (!Number.isInteger(k) || k < 1) {
|
|
458
|
+
return { command: 'invalid', error: '--k must be a positive integer.' };
|
|
459
|
+
}
|
|
460
|
+
if (k > MAX_K) {
|
|
461
|
+
return { command: 'invalid', error: `--k must not exceed ${MAX_K} — each run is k × goldens model+judge calls; a larger value is almost always a typo.` };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const coverageFloor = p.values['coverage-floor'] !== undefined ? Number(p.values['coverage-floor']) : undefined;
|
|
465
|
+
const qualityFloor = p.values['quality-floor'] !== undefined ? Number(p.values['quality-floor']) : undefined;
|
|
466
|
+
if (coverageFloor !== undefined && (!Number.isFinite(coverageFloor) || coverageFloor < COVERAGE_FLOOR_RANGE[0] || coverageFloor > COVERAGE_FLOOR_RANGE[1])) {
|
|
467
|
+
return { command: 'invalid', error: `--coverage-floor must be a number within [${COVERAGE_FLOOR_RANGE.join(', ')}] (a warranted-capture hit-rate); a negative floor would disable the coverage axis.` };
|
|
468
|
+
}
|
|
469
|
+
if (qualityFloor !== undefined && (!Number.isFinite(qualityFloor) || qualityFloor < QUALITY_FLOOR_RANGE[0] || qualityFloor > QUALITY_FLOOR_RANGE[1])) {
|
|
470
|
+
return { command: 'invalid', error: `--quality-floor must be a number within [${QUALITY_FLOOR_RANGE.join(', ')}] (judge scores are integers 1-5); a floor outside the scale would disable the quality axis.` };
|
|
471
|
+
}
|
|
472
|
+
if ((p.flags['accept-regression'] || p.flags.rebaseline) && (p.values.rationale === undefined || String(p.values.rationale).trim() === '')) {
|
|
473
|
+
const flag = p.flags.rebaseline ? '--rebaseline' : '--accept-regression';
|
|
474
|
+
return { command: 'invalid', error: `${flag} requires a non-empty --rationale (the audit trail for the escape hatch).` };
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
command: 'skills-bump',
|
|
478
|
+
skill: p.positionals[0],
|
|
479
|
+
to: p.values.to,
|
|
480
|
+
repo: p.values.repo,
|
|
481
|
+
k,
|
|
482
|
+
coverageFloor,
|
|
483
|
+
qualityFloor,
|
|
484
|
+
rationale: p.values.rationale,
|
|
485
|
+
acceptedBy: p.values['accepted-by'],
|
|
486
|
+
judgeModel: p.values['judge-model'],
|
|
487
|
+
goldensDir: p.values['goldens-dir'],
|
|
488
|
+
domain: p.values.domain,
|
|
489
|
+
acceptRegression: Boolean(p.flags['accept-regression']),
|
|
490
|
+
rebaseline: Boolean(p.flags.rebaseline),
|
|
491
|
+
shapeChange: Boolean(p.flags['shape-change']),
|
|
492
|
+
json: Boolean(p.flags.json),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return { command: 'invalid', error: `Unknown skills subcommand: ${sub} (expected: status, bump).` };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function parseEvalArgs(args) {
|
|
499
|
+
const [sub, ...rest] = args;
|
|
500
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'eval-help' };
|
|
501
|
+
if (sub === 'view') {
|
|
502
|
+
const p = parseOptions(rest, {
|
|
503
|
+
values: ['repo', 'skill', 'goldens-dir', 'port', 'host'],
|
|
504
|
+
flags: ['json', 'no-serve'],
|
|
505
|
+
});
|
|
506
|
+
if (p.help) return { command: 'eval-help' };
|
|
507
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for eval view.` };
|
|
508
|
+
if (p.positionals.length) {
|
|
509
|
+
return { command: 'invalid', error: 'eval view takes no positional arguments (use --skill <name>).' };
|
|
510
|
+
}
|
|
511
|
+
let port;
|
|
512
|
+
if (p.values.port !== undefined) {
|
|
513
|
+
port = Number(p.values.port);
|
|
514
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
515
|
+
return { command: 'invalid', error: '--port must be an integer in 0..65535.' };
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return {
|
|
519
|
+
command: 'eval-view',
|
|
520
|
+
repo: p.values.repo,
|
|
521
|
+
skill: p.values.skill,
|
|
522
|
+
goldensDir: p.values['goldens-dir'],
|
|
523
|
+
port,
|
|
524
|
+
host: p.values.host,
|
|
525
|
+
json: Boolean(p.flags.json),
|
|
526
|
+
serve: !p.flags['no-serve'],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return { command: 'invalid', error: `Unknown eval subcommand: ${sub} (expected: view).` };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function parseHooksArgs(args) {
|
|
533
|
+
const [sub, ...rest] = args;
|
|
534
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'hooks-help' };
|
|
535
|
+
if (sub === 'due-check') {
|
|
536
|
+
const p = parseOptions(rest, { values: ['session', 'dir', 'actor', 'surface'] });
|
|
537
|
+
if (p.help) return { command: 'hooks-help' };
|
|
538
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks due-check.` };
|
|
539
|
+
if (p.positionals.length) {
|
|
540
|
+
return { command: 'invalid', error: 'hooks due-check takes no positional arguments.' };
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
command: 'hooks-due-check',
|
|
544
|
+
session: p.values.session,
|
|
545
|
+
dir: p.values.dir,
|
|
546
|
+
actor: p.values.actor,
|
|
547
|
+
surface: p.values.surface,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (sub === 'sweep-status') {
|
|
551
|
+
const p = parseOptions(rest, { values: ['session', 'dir'], flags: ['json'] });
|
|
552
|
+
if (p.help) return { command: 'hooks-help' };
|
|
553
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks sweep-status.` };
|
|
554
|
+
if (p.positionals.length) {
|
|
555
|
+
return { command: 'invalid', error: 'hooks sweep-status takes no positional arguments.' };
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
command: 'hooks-sweep-status',
|
|
559
|
+
session: p.values.session,
|
|
560
|
+
dir: p.values.dir,
|
|
561
|
+
json: Boolean(p.flags.json),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (sub === 'skill-start') {
|
|
565
|
+
const p = parseOptions(rest, { values: ['dir', 'payload'] });
|
|
566
|
+
if (p.help) return { command: 'hooks-help' };
|
|
567
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks skill-start.` };
|
|
568
|
+
if (p.positionals.length) {
|
|
569
|
+
return { command: 'invalid', error: 'hooks skill-start takes no positional arguments.' };
|
|
570
|
+
}
|
|
571
|
+
return { command: 'hooks-skill-start', dir: p.values.dir, payload: p.values.payload };
|
|
572
|
+
}
|
|
573
|
+
if (sub === 'skill-complete') {
|
|
574
|
+
const p = parseOptions(rest, { values: ['session', 'dir', 'payload', 'boundary-event'], flags: ['json'] });
|
|
575
|
+
if (p.help) return { command: 'hooks-help' };
|
|
576
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks skill-complete.` };
|
|
577
|
+
if (p.positionals.length) {
|
|
578
|
+
return { command: 'invalid', error: 'hooks skill-complete takes no positional arguments.' };
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
command: 'hooks-skill-complete',
|
|
582
|
+
session: p.values.session,
|
|
583
|
+
dir: p.values.dir,
|
|
584
|
+
payload: p.values.payload,
|
|
585
|
+
boundaryEvent: p.values['boundary-event'],
|
|
586
|
+
json: Boolean(p.flags.json),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (sub === 'skill-abandon') {
|
|
590
|
+
const p = parseOptions(rest, { values: ['dir', 'payload'], flags: ['json'] });
|
|
591
|
+
if (p.help) return { command: 'hooks-help' };
|
|
592
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks skill-abandon.` };
|
|
593
|
+
if (p.positionals.length) {
|
|
594
|
+
return { command: 'invalid', error: 'hooks skill-abandon takes no positional arguments.' };
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
command: 'hooks-skill-abandon',
|
|
598
|
+
dir: p.values.dir,
|
|
599
|
+
payload: p.values.payload,
|
|
600
|
+
json: Boolean(p.flags.json),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (sub === 'skill-sweep-stale') {
|
|
604
|
+
const p = parseOptions(rest, { values: ['session', 'dir'], flags: ['json'] });
|
|
605
|
+
if (p.help) return { command: 'hooks-help' };
|
|
606
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for hooks skill-sweep-stale.` };
|
|
607
|
+
if (p.positionals.length) {
|
|
608
|
+
return { command: 'invalid', error: 'hooks skill-sweep-stale takes no positional arguments.' };
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
command: 'hooks-skill-sweep-stale',
|
|
612
|
+
session: p.values.session,
|
|
613
|
+
dir: p.values.dir,
|
|
614
|
+
json: Boolean(p.flags.json),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return {
|
|
618
|
+
command: 'invalid',
|
|
619
|
+
error:
|
|
620
|
+
`Unknown hooks subcommand: ${sub} ` +
|
|
621
|
+
'(expected: due-check | sweep-status | skill-start | skill-complete | skill-abandon | skill-sweep-stale).',
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// `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`.
|
|
627
|
+
function parseAdapterArgs(args) {
|
|
628
|
+
const [sub, ...rest] = args;
|
|
629
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'adapter-help' };
|
|
630
|
+
if (sub === 'test') {
|
|
631
|
+
const p = parseOptions(rest, { values: ['dir', 'domain'], flags: ['json'] });
|
|
632
|
+
if (p.help) return { command: 'adapter-help' };
|
|
633
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for adapter test.` };
|
|
634
|
+
if (p.positionals.length !== 1) {
|
|
635
|
+
return { command: 'invalid', error: 'adapter test takes one <surface> argument (V1: codex).' };
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
command: 'adapter-test',
|
|
639
|
+
surface: p.positionals[0],
|
|
640
|
+
dir: p.values.dir,
|
|
641
|
+
domain: p.values.domain,
|
|
642
|
+
json: Boolean(p.flags.json),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return { command: 'invalid', error: `Unknown adapter subcommand: ${sub} (expected: test).` };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseGuardArgs(args) {
|
|
649
|
+
const p = parseOptions(args, { values: ['dir', 'session', 'surface', 'actor', 'domain'], flags: ['json'] });
|
|
650
|
+
if (p.help) return { command: 'guard-help' };
|
|
651
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for guard.` };
|
|
652
|
+
if (p.positionals.length) {
|
|
653
|
+
return { command: 'invalid', error: 'guard takes no positional prompt argument; pass the prompt on stdin.' };
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
command: 'guard',
|
|
657
|
+
dir: p.values.dir,
|
|
658
|
+
session: p.values.session,
|
|
659
|
+
surface: p.values.surface,
|
|
660
|
+
actor: p.values.actor,
|
|
661
|
+
domain: p.values.domain,
|
|
662
|
+
json: Boolean(p.flags.json),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// search/query share one parser (core-0014 / T5.2): one positional (the query string),
|
|
667
|
+
// scope flags (--domain | --all-domains — contradictory together), --limit, and for
|
|
668
|
+
// query --hops. Semantic defaults (limit 10, hops 1) live in @phronesis/core, not here —
|
|
669
|
+
// omitted flags stay undefined so the library is the single source of the defaults.
|
|
670
|
+
function parseRetrievalArgs(args, which) {
|
|
671
|
+
const values = ['dir', 'domain', 'limit'];
|
|
672
|
+
if (which === 'query') values.push('hops');
|
|
673
|
+
const p = parseOptions(args, { values, flags: ['json', 'all-domains', 'include-artifacts'] });
|
|
674
|
+
if (p.help) return { command: `${which}-help` };
|
|
675
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for ${which}.` };
|
|
676
|
+
if (p.positionals.length > 1) {
|
|
677
|
+
return { command: 'invalid', error: `${which} takes one quoted <query> argument.` };
|
|
678
|
+
}
|
|
679
|
+
if (p.values.domain !== undefined && p.flags['all-domains']) {
|
|
680
|
+
return { command: 'invalid', error: `${which}: --domain and --all-domains contradict — pick one scope.` };
|
|
681
|
+
}
|
|
682
|
+
let limit;
|
|
683
|
+
if (p.values.limit !== undefined) {
|
|
684
|
+
limit = Number(p.values.limit);
|
|
685
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
686
|
+
return { command: 'invalid', error: `${which} --limit must be a positive integer (got "${p.values.limit}").` };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
let hops;
|
|
690
|
+
if (which === 'query' && p.values.hops !== undefined) {
|
|
691
|
+
hops = Number(p.values.hops);
|
|
692
|
+
if (!Number.isInteger(hops) || hops < 0) {
|
|
693
|
+
return { command: 'invalid', error: `query --hops must be a non-negative integer (got "${p.values.hops}").` };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const parsed = {
|
|
697
|
+
command: which,
|
|
698
|
+
query: p.positionals[0],
|
|
699
|
+
dir: p.values.dir,
|
|
700
|
+
domain: p.values.domain,
|
|
701
|
+
allDomains: Boolean(p.flags['all-domains']),
|
|
702
|
+
limit,
|
|
703
|
+
hops,
|
|
704
|
+
json: Boolean(p.flags.json),
|
|
705
|
+
};
|
|
706
|
+
if (p.flags['include-artifacts']) parsed.includeArtifacts = true;
|
|
707
|
+
return parsed;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function parseIndexArgs(args) {
|
|
711
|
+
const [sub, ...rest] = args;
|
|
712
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'index-help' };
|
|
713
|
+
if (sub === 'rebuild') {
|
|
714
|
+
const p = parseOptions(rest, { values: ['dir'], flags: ['json'] });
|
|
715
|
+
if (p.help) return { command: 'index-help' };
|
|
716
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for index rebuild.` };
|
|
717
|
+
if (p.positionals.length) {
|
|
718
|
+
return { command: 'invalid', error: 'index rebuild takes no positional arguments.' };
|
|
719
|
+
}
|
|
720
|
+
return { command: 'index-rebuild', dir: p.values.dir, json: Boolean(p.flags.json) };
|
|
721
|
+
}
|
|
722
|
+
return { command: 'invalid', error: `Unknown index subcommand: ${sub} (expected: rebuild).` };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function parseLintArgs(args) {
|
|
726
|
+
const p = parseOptions(args, { values: ['dir', 'domain', 'stale-months'], flags: ['json', 'all-domains'] });
|
|
727
|
+
if (p.help) return { command: 'lint-help' };
|
|
728
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for lint.` };
|
|
729
|
+
if (p.positionals.length) {
|
|
730
|
+
return { command: 'invalid', error: 'lint takes no positional arguments.' };
|
|
731
|
+
}
|
|
732
|
+
if (p.values.domain !== undefined && p.flags['all-domains']) {
|
|
733
|
+
return { command: 'invalid', error: 'lint: --domain and --all-domains contradict — pick one scope.' };
|
|
734
|
+
}
|
|
735
|
+
let staleMonths;
|
|
736
|
+
if (p.values['stale-months'] !== undefined) {
|
|
737
|
+
staleMonths = Number(p.values['stale-months']);
|
|
738
|
+
if (!Number.isInteger(staleMonths) || staleMonths < 1) {
|
|
739
|
+
return { command: 'invalid', error: `lint --stale-months must be a positive integer (got "${p.values['stale-months']}").` };
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
command: 'lint',
|
|
744
|
+
dir: p.values.dir,
|
|
745
|
+
domain: p.values.domain,
|
|
746
|
+
allDomains: Boolean(p.flags['all-domains']),
|
|
747
|
+
staleMonths,
|
|
748
|
+
json: Boolean(p.flags.json),
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function parseDoctorArgs(args) {
|
|
753
|
+
const p = parseOptions(args, { values: ['dir'], flags: ['json'] });
|
|
754
|
+
if (p.help) return { command: 'doctor-help' };
|
|
755
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for doctor.` };
|
|
756
|
+
if (p.positionals.length) {
|
|
757
|
+
return { command: 'invalid', error: 'doctor takes no positional arguments (pass --dir <install>).' };
|
|
758
|
+
}
|
|
759
|
+
return { command: 'doctor', dir: p.values.dir, json: Boolean(p.flags.json) };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// `phronesis due [--json]` (pure read of the schedule registry), `phronesis due complete
|
|
763
|
+
// <schedule_id>` (the explicit completion floor), and `phronesis due run <schedule_id>`
|
|
764
|
+
// (operator-invoked deterministic executor). core-0051/core-0058 / change 0042.
|
|
765
|
+
function parseDueArgs(args) {
|
|
766
|
+
const [sub, ...rest] = args;
|
|
767
|
+
if (sub === 'complete' || sub === 'run') {
|
|
768
|
+
const p = parseOptions(rest, { values: ['dir'] });
|
|
769
|
+
if (p.help) return { command: 'due-help' };
|
|
770
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for due ${sub}.` };
|
|
771
|
+
if (p.positionals.length !== 1) {
|
|
772
|
+
return { command: 'invalid', error: `due ${sub} takes exactly one <schedule_id> (e.g. \`phronesis due ${sub} lint-weekly\`).` };
|
|
773
|
+
}
|
|
774
|
+
return { command: sub === 'complete' ? 'due-complete' : 'due-run', scheduleId: p.positionals[0], dir: p.values.dir };
|
|
775
|
+
}
|
|
776
|
+
const p = parseOptions(args, { values: ['dir'], flags: ['json'] });
|
|
777
|
+
if (p.help) return { command: 'due-help' };
|
|
778
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for due.` };
|
|
779
|
+
if (p.positionals.length) {
|
|
780
|
+
return { command: 'invalid', error: `Unknown due subcommand: ${p.positionals[0]} (expected: complete | run, or no subcommand for the overdue list).` };
|
|
781
|
+
}
|
|
782
|
+
return { command: 'due', dir: p.values.dir, json: Boolean(p.flags.json) };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function parseEventsArgs(args) {
|
|
786
|
+
const [sub, ...rest] = args;
|
|
787
|
+
if (sub === undefined || sub === '--help' || sub === '-h') return { command: 'events-help' };
|
|
788
|
+
if (sub === 'purge') {
|
|
789
|
+
const p = parseOptions(rest, { values: ['before', 'dir'], flags: ['archive'] });
|
|
790
|
+
if (p.help) return { command: 'events-help' };
|
|
791
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for events purge.` };
|
|
792
|
+
if (p.positionals.length) {
|
|
793
|
+
return { command: 'invalid', error: 'events purge takes no positional arguments.' };
|
|
794
|
+
}
|
|
795
|
+
return { command: 'events-purge', before: p.values.before, archive: Boolean(p.flags.archive), dir: p.values.dir };
|
|
796
|
+
}
|
|
797
|
+
if (sub === 'rotate') {
|
|
798
|
+
const p = parseOptions(rest, { values: ['dir'] });
|
|
799
|
+
if (p.help) return { command: 'events-help' };
|
|
800
|
+
if (p.error) return { command: 'invalid', error: `${p.error} for events rotate.` };
|
|
801
|
+
if (p.positionals.length) {
|
|
802
|
+
return { command: 'invalid', error: 'events rotate takes no positional arguments.' };
|
|
803
|
+
}
|
|
804
|
+
return { command: 'events-rotate', dir: p.values.dir };
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
command: 'invalid',
|
|
808
|
+
error: `Unknown events subcommand: ${sub} (expected: purge | rotate).`,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function showHelp() {
|
|
813
|
+
console.log(
|
|
814
|
+
`
|
|
815
|
+
phronesis v${version} — phronesis-grounded workspace architecture
|
|
816
|
+
|
|
817
|
+
Usage: phronesis <command>
|
|
818
|
+
|
|
819
|
+
Commands:
|
|
820
|
+
init [dir] Create a Phronesis installation (default dir: phronesis-installation)
|
|
821
|
+
export [dir] Tar an installation into a portable archive (default dir: current)
|
|
822
|
+
validate [dir] Check compiled + codex objects against the ontology contract
|
|
823
|
+
search <query> Stage 1 retrieval — BM25 over active-domain compiled/ + shared codex
|
|
824
|
+
query <query> Stage 1+2 retrieval — BM25 seeds expanded through typed links, sources cited
|
|
825
|
+
index <sub> Maintain the derived machine search index (rebuild)
|
|
826
|
+
lint Read-only deterministic workspace health report
|
|
827
|
+
doctor Read-only install-health overview (is my install wired?)
|
|
828
|
+
due [sub] Show overdue schedule entries; complete or run one
|
|
829
|
+
links <object> Walk typed links from an object — the N-hop connected subgraph
|
|
830
|
+
event <sub> Emit (emit) or read (tail) the typed event stream
|
|
831
|
+
events <sub> Maintain the event stream (purge, rotate)
|
|
832
|
+
registry <sub> Show the install's action-type + hook registry (status)
|
|
833
|
+
skills <sub> Show the install's skill registry (status)
|
|
834
|
+
eval <sub> Local eval data viewer (view) — goldens, scores, candidate output
|
|
835
|
+
hooks <sub> Session-boundary hooks: due-check, sweep-status
|
|
836
|
+
adapter <sub> Per-surface adapter self-check (test codex)
|
|
837
|
+
guard Prompt-submit recall guard (stdin-only)
|
|
838
|
+
ingest <source> Stage one URL, file, or stdin paste into domain raw/
|
|
839
|
+
|
|
840
|
+
Options:
|
|
841
|
+
--help, -h Show this help
|
|
842
|
+
--version, -v Show version number
|
|
843
|
+
|
|
844
|
+
Run 'phronesis <command> --help' for command options.
|
|
845
|
+
`.trim(),
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function showInitHelp() {
|
|
850
|
+
console.log(
|
|
851
|
+
`
|
|
852
|
+
Usage: phronesis init [dir] [--force] [--domain <slug>]
|
|
853
|
+
[--name <name>] [--role <role>] [--company <co>] [--working-style <style>]
|
|
854
|
+
|
|
855
|
+
Creates a three-layer installation at [dir]:
|
|
856
|
+
codex/ shared principles across all domains (seed + personal)
|
|
857
|
+
domains/<slug>/ a domain workspace (raw + compiled)
|
|
858
|
+
skills/ .phronesis/
|
|
859
|
+
|
|
860
|
+
A minimal operator profile (name + role) fills USER.md and AGENTS.md so a fresh
|
|
861
|
+
install does not load [Name]/[Role] placeholders. On an interactive terminal, a
|
|
862
|
+
missing name/role is prompted; a non-interactive (scripted/CI) init never prompts
|
|
863
|
+
or blocks — it completes and leaves the placeholders.
|
|
864
|
+
|
|
865
|
+
Options:
|
|
866
|
+
--force, -f Replace a non-empty target directory
|
|
867
|
+
--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)
|
|
872
|
+
`.trim(),
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function showExportHelp() {
|
|
877
|
+
console.log(
|
|
878
|
+
`
|
|
879
|
+
Usage: phronesis export [dir] [--out <file>]
|
|
880
|
+
|
|
881
|
+
Tars a Phronesis installation into a portable gzip archive. The export is a
|
|
882
|
+
faithful snapshot — plain Markdown plus config, relative symlinks preserved —
|
|
883
|
+
that opens in Obsidian or VS Code with no Phronesis runtime present.
|
|
884
|
+
|
|
885
|
+
Arguments:
|
|
886
|
+
dir The installation to export (default: current directory)
|
|
887
|
+
|
|
888
|
+
Options:
|
|
889
|
+
--out, -o <file> Archive path (default: <dir>.tar.gz beside the installation)
|
|
890
|
+
`.trim(),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function showValidateHelp() {
|
|
895
|
+
console.log(
|
|
896
|
+
`
|
|
897
|
+
Usage: phronesis validate [dir]
|
|
898
|
+
|
|
899
|
+
Checks every typed object in domains/*/compiled/ and the installation codex/ against
|
|
900
|
+
the ontology frontmatter contract (specs/ontology.md): required fields, enums, dates,
|
|
901
|
+
link vocabulary, and link-target grammar + resolution. Raw sources are not validated.
|
|
902
|
+
|
|
903
|
+
Exits 0 when the workspace is valid; non-zero with a per-object error report otherwise.
|
|
904
|
+
|
|
905
|
+
Arguments:
|
|
906
|
+
dir The installation to validate (default: current directory)
|
|
907
|
+
`.trim(),
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function showLinksHelp() {
|
|
912
|
+
console.log(
|
|
913
|
+
`
|
|
914
|
+
Usage: phronesis links <object> [--dir <install>] [--hops N] [--direction out|in|both] [--json]
|
|
915
|
+
|
|
916
|
+
Walks typed links (specs/ontology.md) from an object across domains/*/compiled/ and the
|
|
917
|
+
installation codex/, returning the N-hop connected subgraph — Stage 2 retrieval. A
|
|
918
|
+
substrate command: skills and the operator-facing query compose the same walk.
|
|
919
|
+
|
|
920
|
+
<object> may be an install-relative path, a bare object name, or a {type-dir}/{name} suffix.
|
|
921
|
+
Raw-source and not-yet-compiled (forward) targets appear as terminal leaves.
|
|
922
|
+
|
|
923
|
+
Arguments:
|
|
924
|
+
object The object to walk from
|
|
925
|
+
|
|
926
|
+
Options:
|
|
927
|
+
--dir <install> The installation to read (default: current directory)
|
|
928
|
+
--hops N How many hops to walk (default: 2)
|
|
929
|
+
--direction <d> Edge directions to follow: out | in | both (default: both)
|
|
930
|
+
--json Emit the subgraph as JSON
|
|
931
|
+
`.trim(),
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function showEventHelp() {
|
|
936
|
+
console.log(
|
|
937
|
+
`
|
|
938
|
+
Usage:
|
|
939
|
+
phronesis event emit <type> --session <id> [--payload '<json>'] [--actor <a>] [--surface <s>] [--dir <install>]
|
|
940
|
+
phronesis event tail [--type <t>] [--since <iso-ts>] [--follow] [--dir <install>]
|
|
941
|
+
|
|
942
|
+
The universal emission + read path over the typed event stream (specs/event-log.md). Every
|
|
943
|
+
surface and agent shells out to 'event emit'. The installation root is discovered by walking
|
|
944
|
+
up from the current directory to the nearest .phronesis/registry.json; --dir overrides.
|
|
945
|
+
|
|
946
|
+
emit:
|
|
947
|
+
<type> A V1 event type or a connector.<name>.<event> extension. Reserved types
|
|
948
|
+
are refused: file.* (the V1.5 daemon's filesystem watch) and outcome.due
|
|
949
|
+
(the session-start due-check, 'phronesis hooks due-check').
|
|
950
|
+
--session <id> Session the event belongs to (required).
|
|
951
|
+
--payload '<json>' Event payload object (default {}). Message text / recall query are
|
|
952
|
+
dropped unless event_log.capture_message_text is enabled in config.json.
|
|
953
|
+
--actor <a> Defaults to $PHRONESIS_ACTOR, else 'agent'.
|
|
954
|
+
--surface <s> Defaults to 'cli'.
|
|
955
|
+
|
|
956
|
+
tail:
|
|
957
|
+
--type <t> Only show events of this type.
|
|
958
|
+
--since <iso-ts> Only show events at or after this ISO-8601 timestamp.
|
|
959
|
+
--follow Stream new events as they are appended.
|
|
960
|
+
`.trim(),
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function showActHelp() {
|
|
965
|
+
console.log(
|
|
966
|
+
`
|
|
967
|
+
Usage: phronesis act <type> --json '<payload>' --session <id> [--domain <slug>] [--actor <a>] [--surface <s>] [--invocation-id <id>] [--dir <install>]
|
|
968
|
+
|
|
969
|
+
The action-type execution floor (specs/ontology.md §"Action type contract") — plumbing,
|
|
970
|
+
not porcelain: skills compose it; it is not a promoted operator verb. One shared core
|
|
971
|
+
runs every action: schema validation, permission gate, action_type.requested/committed
|
|
972
|
+
events, and a staged atomic write that must pass the ontology contract before it lands.
|
|
973
|
+
|
|
974
|
+
Registered types: log_decision, enrich_person, update_project_state,
|
|
975
|
+
track_commitment, record_outcome, promote_to_codex (handlers: core-0009/0010),
|
|
976
|
+
compile (the agentic quality-gate writer for insight + organization, core-0017 — review
|
|
977
|
+
its untrusted drafts with 'phronesis compile accept|reject'). The install's EFFECTIVE
|
|
978
|
+
table (operator overrides in .phronesis/registry.json action_types) is shown by
|
|
979
|
+
'phronesis registry status'.
|
|
980
|
+
|
|
981
|
+
<type> The action type to run.
|
|
982
|
+
--json '<payload>' The action's SEMANTIC inputs (title, insight, links, …), schema-
|
|
983
|
+
validated. Execution context never goes here.
|
|
984
|
+
--session <id> Session the action belongs to (required).
|
|
985
|
+
--domain <slug> Domain workspace (default: the domains/<slug>/ the cwd is inside,
|
|
986
|
+
else "pm"). Must be registered in the installation.
|
|
987
|
+
--actor <a> Defaults to $PHRONESIS_ACTOR, else 'agent'.
|
|
988
|
+
--surface <s> Defaults to 'cli'.
|
|
989
|
+
--invocation-id <i> Attribution provenance for a surrounding skill invocation. It is
|
|
990
|
+
caller-controlled (not an authority); adapters and the eval harness
|
|
991
|
+
may supply it. PHRONESIS_INVOCATION_ID is the equivalent env input.
|
|
992
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json).
|
|
993
|
+
|
|
994
|
+
Approval-gated actions refuse without an approval signal: the env var
|
|
995
|
+
PHRONESIS_APPROVAL_SOURCE (e.g. operator:interactive), set by an approving adapter and
|
|
996
|
+
recorded verbatim as approval_source on action_type.committed.
|
|
997
|
+
`.trim(),
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function showIngestHelp() {
|
|
1002
|
+
console.log(
|
|
1003
|
+
`
|
|
1004
|
+
Usage:
|
|
1005
|
+
phronesis ingest <url|file|dir> --domain <slug> [--lane <lane>] [--trust trusted|untrusted] [--json] [--dir <install>]
|
|
1006
|
+
phronesis ingest --paste --domain <slug> [--lane <lane>] [--dir <install>]
|
|
1007
|
+
phronesis ingest - --domain <slug> --title <title> [--slug <slug>] [--lane <lane>] [--dir <install>]
|
|
1008
|
+
phronesis ingest - --domain <slug> --lane <lane> --source-connector <name>
|
|
1009
|
+
--source-external-id <id> --source-timestamp <iso> --title <title> [--slug <slug>] [--json]
|
|
1010
|
+
|
|
1011
|
+
Stages one operator-pointed source, or every readable text file under a directory,
|
|
1012
|
+
into domains/<slug>/raw/<lane>/ with connector provenance frontmatter, then stops.
|
|
1013
|
+
V1 ingest writes no compiled/ or codex/ state, emits no events, and does not auto-run
|
|
1014
|
+
compile.
|
|
1015
|
+
|
|
1016
|
+
Arguments:
|
|
1017
|
+
<url|file|dir> http(s) URL to fetch, local file to read, or directory to bulk-stage.
|
|
1018
|
+
Use - for titled stdin.
|
|
1019
|
+
|
|
1020
|
+
Options:
|
|
1021
|
+
--domain <slug> Registered domain to write into (required)
|
|
1022
|
+
--lane <lane> Raw content lane (default: inbox; e.g. transcripts)
|
|
1023
|
+
--paste Read source text from stdin
|
|
1024
|
+
--trust <tier> untrusted by default. trusted is honored only for local files.
|
|
1025
|
+
--title <title> Required when source is -; used to derive the staged filename slug.
|
|
1026
|
+
--slug <slug> Optional exact filename slug override for titled stdin.
|
|
1027
|
+
--source-connector <name> Bridge provenance connector name.
|
|
1028
|
+
--source-external-id <id> Bridge stable external item id.
|
|
1029
|
+
--source-timestamp <iso> Bridge source timestamp.
|
|
1030
|
+
--json Emit the ingest report as JSON
|
|
1031
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1032
|
+
`.trim(),
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function showCompileHelp() {
|
|
1037
|
+
console.log(
|
|
1038
|
+
`
|
|
1039
|
+
Usage:
|
|
1040
|
+
phronesis compile accept <draft_path> --session <id> [--dir <install>] [--surface <s>]
|
|
1041
|
+
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]
|
|
1043
|
+
|
|
1044
|
+
The operator review primitives over a compile candidate (specs/security.md draft
|
|
1045
|
+
lifecycle; core-0017 H9). An untrusted compile stages a draft under .phronesis/drafts/ and
|
|
1046
|
+
emits compile.candidate — no canonical state changes until you accept it. Both events are
|
|
1047
|
+
operator-attributed by contract — there is no --actor flag.
|
|
1048
|
+
|
|
1049
|
+
accept: Validate the draft AS its canonical compiled/ shape, move it into compiled/
|
|
1050
|
+
(overwriting on an update, fresh-writing on a create), discard the draft, and
|
|
1051
|
+
emit action_type.requested → action_type.committed (approval_source recorded —
|
|
1052
|
+
acceptance IS the approval) → compile.accepted. The committed event refreshes the
|
|
1053
|
+
domain's active-context projection (the post-compile hook).
|
|
1054
|
+
reject: Discard the draft and emit compile.rejected { draft_path, reason }. No canonical
|
|
1055
|
+
state changes. A draft is transient (a temp), so discarding it is permitted.
|
|
1056
|
+
review: The batch-approval UX over the same per-draft accept move (T7.5). Lists pending
|
|
1057
|
+
drafts and their fail-closed classification; --approve-all promotes the
|
|
1058
|
+
provably-clean set as ONE logical approval — every compile.accepted shares one
|
|
1059
|
+
batch_id + the operator's one --rationale (change 0025). A draft is EXCLUDED from
|
|
1060
|
+
approve-all (left for individual review) when its LATEST compile.candidate is
|
|
1061
|
+
suspicious_payload-flagged; when it has NO compile.candidate event at all; when that
|
|
1062
|
+
latest candidate carries NO content_digest (not provably clean); or when the on-disk
|
|
1063
|
+
draft's sha-256 no longer matches that digest (the bytes were edited after the clean
|
|
1064
|
+
candidate — change 0027). A benign re-draft (several clean candidates, latest clean,
|
|
1065
|
+
digest matching the current bytes) stays eligible. A mid-batch crash is recoverable:
|
|
1066
|
+
--resume re-drives the batch from a durable .phronesis/ journal, reconciling every
|
|
1067
|
+
missing post-move boundary in order (committed -> projection -> compile.accepted).
|
|
1068
|
+
|
|
1069
|
+
<draft_path> (accept/reject) The .phronesis/drafts/{domain}/{type-dir}/{name}.md candidate.
|
|
1070
|
+
--session <id> Review session the events belong to (required).
|
|
1071
|
+
--reason '<why>' (reject) recorded on compile.rejected.
|
|
1072
|
+
--domain <slug> (review) Domain whose drafts to review (default: pm).
|
|
1073
|
+
--approve-all (review) Promote the provably-clean set as one batch.
|
|
1074
|
+
--rationale '<why>'(review) Required with --approve-all; recorded on every batch compile.accepted.
|
|
1075
|
+
--resume (review) Reconcile a crashed batch from the durable journal.
|
|
1076
|
+
--json (review) Emit the plan / result as JSON.
|
|
1077
|
+
|
|
1078
|
+
accept consumes the approval signal from env PHRONESIS_APPROVAL_SOURCE (the same channel
|
|
1079
|
+
as 'phronesis act'); when unset it records 'operator:compile-accept'.
|
|
1080
|
+
`.trim(),
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function showActiveContextHelp() {
|
|
1085
|
+
console.log(
|
|
1086
|
+
`
|
|
1087
|
+
Usage: phronesis active-context refresh --domain <slug> [--dir <install>] [--json]
|
|
1088
|
+
|
|
1089
|
+
Regenerate a domain's tracked active-context projection,
|
|
1090
|
+
domains/<slug>/ACTIVE_CONTEXT.md (specs/compile-retrieve-loop.md §"Generated-state
|
|
1091
|
+
hygiene"; core-0017 H5) — a derived-but-tracked summary of the domain's compiled state,
|
|
1092
|
+
distinct from the gitignored machine index under .phronesis/index/. The post-compile hook
|
|
1093
|
+
shells out to this on every compile commit; 'phronesis validate' flags the projection
|
|
1094
|
+
stale when a compiled object is newer than its entry.
|
|
1095
|
+
|
|
1096
|
+
Options:
|
|
1097
|
+
--domain <slug> The domain whose projection to refresh (required; must be registered)
|
|
1098
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1099
|
+
--json Emit the refresh result as JSON
|
|
1100
|
+
`.trim(),
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function showRegistryHelp() {
|
|
1105
|
+
console.log(
|
|
1106
|
+
`
|
|
1107
|
+
Usage: phronesis registry status [--dir <install>] [--json]
|
|
1108
|
+
|
|
1109
|
+
Shows the install's action-type registry (specs/ontology.md §"Action type contract";
|
|
1110
|
+
specs/trust-scopes.md Scope 1): where the table comes from (.phronesis/registry.json
|
|
1111
|
+
action_types vs built-in defaults), every type's effective permission and its relation
|
|
1112
|
+
to the code default, the registered target layout, and the payload schema.
|
|
1113
|
+
|
|
1114
|
+
Operator overrides live in .phronesis/registry.json within a safety lattice whose floor
|
|
1115
|
+
is compiled into @phronesis/core: tightening any permission is allowed, loosening a
|
|
1116
|
+
floor action (record_outcome, promote_to_codex) fails closed at load, layouts and
|
|
1117
|
+
schema loosenings are refused. On an invalid registry this prints the load errors and
|
|
1118
|
+
exits non-zero — the action layer is disabled until the registry is fixed.
|
|
1119
|
+
|
|
1120
|
+
Options:
|
|
1121
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1122
|
+
--json Emit the effective registry as JSON
|
|
1123
|
+
`.trim(),
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function showSkillsHelp() {
|
|
1128
|
+
console.log(
|
|
1129
|
+
`
|
|
1130
|
+
Usage: phronesis skills status [--dir <install>] [--json]
|
|
1131
|
+
phronesis skills bump <skill> --to <model_id>@<date> [options]
|
|
1132
|
+
|
|
1133
|
+
status — Shows the install's skill registry (specs/skills.md §"Skill registry"): every
|
|
1134
|
+
skill declared in .phronesis/registry.json's skills section, loaded from
|
|
1135
|
+
skills/{name}/SKILL.md and validated against the frontmatter contract (skills.md
|
|
1136
|
+
§"Frontmatter contract") plus the runtime-free degrade contract (§"Runtime-free
|
|
1137
|
+
invocation", change 0013).
|
|
1138
|
+
|
|
1139
|
+
On an invalid registry or any invalid registered skill this prints the load errors and
|
|
1140
|
+
exits non-zero — the skills layer is disabled until it is fixed. skills/ dirs without a
|
|
1141
|
+
registry entry are listed as unregistered (they do not load).
|
|
1142
|
+
|
|
1143
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1144
|
+
--json Emit the resolved skill records as JSON
|
|
1145
|
+
|
|
1146
|
+
bump — The deployment-unit gate (specs/eval-discipline.md §"Prompt + model is the
|
|
1147
|
+
deployment unit"). Runs the candidate-execution substrate (core-0028) k times under the
|
|
1148
|
+
proposed prompt_pin, aggregates to a STRICT-min floor on BOTH axes (quality + coverage),
|
|
1149
|
+
refuses the comparison if the evaluation identity drifted, blocks an either-axis
|
|
1150
|
+
regression vs the previous accepted pin, increments the skill version, and writes the new
|
|
1151
|
+
pin to skills/<skill>/SKILL.md ONLY on pass — appending a baseline record to
|
|
1152
|
+
skills/<skill>/evals/PINS.md. A bare model bump that regresses is caught before it ships
|
|
1153
|
+
(the Voiceflow safeguard). bump is a dev/release op on git-tracked source: it runs against
|
|
1154
|
+
a repo root (skills/ + changes/ present), never an install.
|
|
1155
|
+
|
|
1156
|
+
--to <pin> Proposed prompt_pin: {model_id}@{date} (required)
|
|
1157
|
+
--repo <path> Repo root (default: cwd; must carry skills/ and changes/)
|
|
1158
|
+
--k <n> Reliability runs per golden (default: 5; floor, not mean)
|
|
1159
|
+
--shape-change The prompt edit changes output shape → MAJOR version bump
|
|
1160
|
+
(default: model-only swap → MINOR, skills.md:43)
|
|
1161
|
+
--accept-regression Proceed over a real, stable-identity regression; logs a structured
|
|
1162
|
+
override record to PINS.md (does NOT bypass identity drift or
|
|
1163
|
+
fabrication)
|
|
1164
|
+
--rebaseline Reset the comparison basis after evaluation-identity drift: re-seed
|
|
1165
|
+
the baseline under the new identity (absolute floor on both axes,
|
|
1166
|
+
no regression check) and write a rebaseline record
|
|
1167
|
+
--coverage-floor <f> Min per-run warranted-capture hit-rate (default: 1.0)
|
|
1168
|
+
--quality-floor <f> Min per-criterion score across k runs (default: 4)
|
|
1169
|
+
--rationale <text> Recorded on an override / rebaseline record
|
|
1170
|
+
--accepted-by <who> Recorded on the PINS.md record (default: operator)
|
|
1171
|
+
--judge-model <m> Judge model for the L2 scores (default: sonnet)
|
|
1172
|
+
--goldens-dir <path> Override the golden corpus dir (default:
|
|
1173
|
+
$PHRONESIS_INSTALL/corpus/skill-goldens/<skill>)
|
|
1174
|
+
--domain <name> Active domain the goldens run under (default: pm)
|
|
1175
|
+
--json Emit the gate decision as JSON
|
|
1176
|
+
|
|
1177
|
+
A fabrication in any run, a quality score below the rubric floor (≥4), or a missed
|
|
1178
|
+
absolute floor on a first-bump/--rebaseline seed is a hard block — never overridable by
|
|
1179
|
+
--accept-regression (which overrides only a relative dip that stays above the floor). A
|
|
1180
|
+
non-completing substrate run (timeout/error) blocks the gate rather than counting as a
|
|
1181
|
+
candidate. An unsigned corpus still runs the gate; the result carries gate_status:
|
|
1182
|
+
provisional (never skipped, never a false green). bump is single-flight: do not run two
|
|
1183
|
+
bumps of the same skill concurrently (it writes git-tracked source; serialization is the
|
|
1184
|
+
daemon's job, T4.1/V1.5).
|
|
1185
|
+
`.trim(),
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function showEvalHelp() {
|
|
1190
|
+
console.log(
|
|
1191
|
+
`
|
|
1192
|
+
Usage: phronesis eval view [--skill <name>] [--repo <path>] [options]
|
|
1193
|
+
|
|
1194
|
+
The tiny LOCAL eval data viewer (specs/eval-discipline.md §"Custom data viewer"). Serves a
|
|
1195
|
+
localhost-only page rendering, per skill: the golden list; input -> expected ->
|
|
1196
|
+
model_default (from the goldens); L2 per-criterion means + below-floor (failed) criteria
|
|
1197
|
+
(from results/aggregate.json). The "actual" (candidate output) column appears only once
|
|
1198
|
+
core-0028's candidate runner has persisted output to results/candidates/ — it is never read
|
|
1199
|
+
from runs/*.json (judge scores only) and never invented.
|
|
1200
|
+
|
|
1201
|
+
It binds localhost ONLY, writes nothing into the repo, and exposes nothing over the network
|
|
1202
|
+
(the operator's goldens carry real names). The repo root (skills/ + changes/) locates the
|
|
1203
|
+
rubrics + gitignored candidate results; goldens resolve from the install
|
|
1204
|
+
($PHRONESIS_INSTALL/corpus/skill-goldens/<skill>, change 0023). Refuses outside a repo root.
|
|
1205
|
+
|
|
1206
|
+
Options:
|
|
1207
|
+
--skill <name> Render only this skill (default: all skills with eval data)
|
|
1208
|
+
--repo <path> Repo root (default: cwd; must carry skills/ and changes/)
|
|
1209
|
+
--goldens-dir <path> Override the golden corpus dir (default:
|
|
1210
|
+
$PHRONESIS_INSTALL/corpus/skill-goldens/<skill>)
|
|
1211
|
+
--port <n> Localhost port (default: an ephemeral port)
|
|
1212
|
+
--host <addr> Bind address (default: 127.0.0.1; off-localhost is refused)
|
|
1213
|
+
--no-serve Print the rendered HTML to stdout instead of serving
|
|
1214
|
+
--json List the skills that would be rendered, as JSON
|
|
1215
|
+
`.trim(),
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function showHooksHelp() {
|
|
1220
|
+
console.log(
|
|
1221
|
+
`
|
|
1222
|
+
Usage:
|
|
1223
|
+
phronesis hooks due-check --session <id> [--actor <a>] [--surface <s>] [--dir <install>]
|
|
1224
|
+
phronesis hooks sweep-status --session <id> [--json] [--dir <install>]
|
|
1225
|
+
phronesis hooks skill-start --payload '<hook-json>' [--dir <install>]
|
|
1226
|
+
phronesis hooks skill-complete --session <id> [--payload '<hook-json>'] [--boundary-event <Stop|SubagentStop>] [--json] [--dir <install>]
|
|
1227
|
+
phronesis hooks skill-abandon --payload '<hook-json>' [--json] [--dir <install>]
|
|
1228
|
+
phronesis hooks skill-sweep-stale --session <id> [--json] [--dir <install>]
|
|
1229
|
+
|
|
1230
|
+
Session-boundary hook support (specs/hooks.md §"Emission discipline"; change 0010 D4).
|
|
1231
|
+
The scaffolded Claude Code hooks (.phronesis/hooks/session-start.sh, session-sweep.sh,
|
|
1232
|
+
skill-lifecycle.sh) shell out to these; any Tier-1 surface adapter can do the same.
|
|
1233
|
+
|
|
1234
|
+
due-check: Computes due review_dates across every domain's decisions (review_date
|
|
1235
|
+
<= today, no outcome recorded), emits one outcome.due per due decision
|
|
1236
|
+
(the V1 authoritative emitter — the daemon's date-watch takes over at
|
|
1237
|
+
V1.5), and prints the due list with a record_outcome pointer. Run it at
|
|
1238
|
+
session start; the printout is the review surface.
|
|
1239
|
+
|
|
1240
|
+
sweep-status: Reports whether this session has emitted its capture-sweep marker — a
|
|
1241
|
+
session.end event with a sweep payload ({"sweep":{"candidates":N}};
|
|
1242
|
+
candidates 0 is the honest negative). Prints "swept" (exit 0) or
|
|
1243
|
+
"pending" (exit 1); the scaffolded Stop hook keys its block-once
|
|
1244
|
+
capture prompt on it.
|
|
1245
|
+
|
|
1246
|
+
skill-start / skill-complete / skill-abandon / skill-sweep-stale:
|
|
1247
|
+
Claude Code skill lifecycle adapter plumbing. skill-start consumes
|
|
1248
|
+
PreToolUse(Skill) or UserPromptExpansion hook JSON, mints an
|
|
1249
|
+
adapter-owned invocation_id, emits skill.invoked, and writes the
|
|
1250
|
+
transient .phronesis/invocations/ record that act reads. Complete emits
|
|
1251
|
+
skill.completed and clears it; abandon/stale-sweep clear without a
|
|
1252
|
+
synthetic completion.
|
|
1253
|
+
`.trim(),
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function showAdapterHelp() {
|
|
1258
|
+
console.log(
|
|
1259
|
+
`
|
|
1260
|
+
Usage: phronesis adapter test <surface> [--dir <install>] [--domain <slug>] [--json]
|
|
1261
|
+
|
|
1262
|
+
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 test.
|
|
1264
|
+
|
|
1265
|
+
adapter test codex — asserts the deterministic mechanics of the project-local Codex
|
|
1266
|
+
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 hand off to
|
|
1268
|
+
the shared .phronesis/hooks/ scripts, and an unswept Stop yields {"decision":"block"}.
|
|
1269
|
+
It reports the compliance LEVEL — Level 1 (reinforcement: recall / due-check / sweep) and
|
|
1270
|
+
Level 2 (skill-lifecycle), which is PARTIAL on Codex (no UserPromptExpansion/StopFailure;
|
|
1271
|
+
no fabricated skill.invoked/completed) — and points at the operator-verified desktop-app
|
|
1272
|
+
checklist section in docs/surface-setup.md, the half a CLI cannot auto-assert.
|
|
1273
|
+
It is read-only: no canonical state is written and no events are emitted.
|
|
1274
|
+
|
|
1275
|
+
Open Codex AT the domain workspace dir (Codex loads .codex/ only from the opened
|
|
1276
|
+
directory, no parent-walk) and trust the project hooks (CLI /hooks, or the app's
|
|
1277
|
+
Settings → Coding → Hooks). Editing the config re-hashes the hooks → re-trust required.
|
|
1278
|
+
|
|
1279
|
+
Options:
|
|
1280
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1281
|
+
--domain <slug> Check one domain's .codex/ (default: every registered domain)
|
|
1282
|
+
--json Emit the compliance report as JSON
|
|
1283
|
+
`.trim(),
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function showGuardHelp() {
|
|
1288
|
+
console.log(
|
|
1289
|
+
`
|
|
1290
|
+
Usage: phronesis guard --dir <install> --session <id> [--surface <s>] [--actor <a>] [--domain <slug>] [--json]
|
|
1291
|
+
|
|
1292
|
+
Pre-answer context relevance guard (specs/compile-retrieve-loop.md §"Context relevance
|
|
1293
|
+
guard"). The prompt is read from stdin only: raw text, or a JSON envelope with
|
|
1294
|
+
{"prompt":"...", "session_id":"..."} mirroring a surface UserPromptSubmit payload.
|
|
1295
|
+
There is no --prompt flag, so prompt text never rides argv.
|
|
1296
|
+
|
|
1297
|
+
When an owned-context advice/action prompt is detected, the guard emits
|
|
1298
|
+
recall.candidate, runs in-process @phronesis/core retrieval over the active domain plus
|
|
1299
|
+
codex/personal, emits recall.injected, and returns either a recall packet or an
|
|
1300
|
+
honest-negative packet. Not-fired prompts return {"fired":false} and emit no events.
|
|
1301
|
+
|
|
1302
|
+
Options:
|
|
1303
|
+
--dir <install> Installation root (required; cwd is not trusted)
|
|
1304
|
+
--session <id> Session id (required)
|
|
1305
|
+
--surface <s> Surface name (default: cli)
|
|
1306
|
+
--actor <a> Actor (default: $PHRONESIS_ACTOR, else agent)
|
|
1307
|
+
--domain <slug> Domain scope override
|
|
1308
|
+
--json Emit the machine-readable guard result
|
|
1309
|
+
`.trim(),
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function showSearchHelp() {
|
|
1314
|
+
console.log(
|
|
1315
|
+
`
|
|
1316
|
+
Usage: phronesis search "<query>" [--domain <slug> | --all-domains] [--include-artifacts] [--limit N] [--json] [--dir <install>]
|
|
1317
|
+
|
|
1318
|
+
Stage 1 retrieval (specs/compile-retrieve-loop.md §"Two-stage retrieval"): in-process
|
|
1319
|
+
BM25 over the active domain's compiled/ objects plus the shared codex. Freshness is
|
|
1320
|
+
read-time — the derived machine index (.phronesis/index/) is verified against disk and
|
|
1321
|
+
repaired on every load, so results always reflect current state (no daemon needed).
|
|
1322
|
+
|
|
1323
|
+
Zero hits is an honest negative, not an error: empty_result: true, exit 0.
|
|
1324
|
+
|
|
1325
|
+
Arguments:
|
|
1326
|
+
query The search text (quote it)
|
|
1327
|
+
|
|
1328
|
+
Options:
|
|
1329
|
+
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else "${DEFAULT_DOMAIN}")
|
|
1330
|
+
--all-domains Search every domain (default scope is one domain + codex)
|
|
1331
|
+
--include-artifacts Explicitly include durable artifacts for this search (not indexed by default)
|
|
1332
|
+
--limit N Max results (default: 10)
|
|
1333
|
+
--json Emit the contract envelope as JSON
|
|
1334
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1335
|
+
`.trim(),
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function showQueryHelp() {
|
|
1340
|
+
console.log(
|
|
1341
|
+
`
|
|
1342
|
+
Usage: phronesis query "<question>" [--domain <slug> | --all-domains] [--include-artifacts] [--limit N] [--hops N] [--json] [--dir <install>]
|
|
1343
|
+
|
|
1344
|
+
Two-stage retrieval (specs/compile-retrieve-loop.md): Stage 1 BM25 finds seed objects,
|
|
1345
|
+
Stage 2 walks each seed's typed links into its connected subgraph — the related
|
|
1346
|
+
decisions, the people involved, and the raw sources they cite (per-result "sources").
|
|
1347
|
+
Semantic rerank (Stage 3) is V1.5+/BYO; this surface stays the same.
|
|
1348
|
+
|
|
1349
|
+
Options:
|
|
1350
|
+
--domain <slug> Domain scope (default: the domains/<slug>/ the cwd is inside, else "${DEFAULT_DOMAIN}")
|
|
1351
|
+
--all-domains Query every domain (default scope is one domain + codex)
|
|
1352
|
+
--include-artifacts Explicitly include durable artifacts for this query (not indexed by default)
|
|
1353
|
+
--limit N Max seed results (default: 10)
|
|
1354
|
+
--hops N Link-walk depth from each seed (default: 1)
|
|
1355
|
+
--json Emit the contract envelope as JSON
|
|
1356
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1357
|
+
`.trim(),
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function showLintHelp() {
|
|
1362
|
+
console.log(
|
|
1363
|
+
`
|
|
1364
|
+
Usage: phronesis lint [--domain <slug> | --all-domains] [--stale-months N] [--json] [--dir <install>]
|
|
1365
|
+
|
|
1366
|
+
Read-only deterministic workspace health report over the active domain's compiled/
|
|
1367
|
+
objects plus the shared codex. This is the mechanical foundation for the lint skill:
|
|
1368
|
+
orphans, supersedes/superseded-by gaps, stale forward refs, codex citation defects,
|
|
1369
|
+
seed-adoption hygiene, and relationships value drift.
|
|
1370
|
+
|
|
1371
|
+
It writes nothing: no compiled edits, no raw/degraded captures, no events, no drafts,
|
|
1372
|
+
and no machine-index repair.
|
|
1373
|
+
|
|
1374
|
+
Options:
|
|
1375
|
+
--domain <slug> Domain scope (default: the active domain, else "${DEFAULT_DOMAIN}")
|
|
1376
|
+
--all-domains Lint every registered domain
|
|
1377
|
+
--stale-months N Staleness threshold for forward refs and seed adoption (default: 6)
|
|
1378
|
+
--json Emit the machine-readable lint envelope
|
|
1379
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1380
|
+
`.trim(),
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function showDoctorHelp() {
|
|
1385
|
+
console.log(
|
|
1386
|
+
`
|
|
1387
|
+
Usage: phronesis doctor [--dir <install>] [--json]
|
|
1388
|
+
|
|
1389
|
+
Read-only install-health overview — the dogfood "is my install wired correctly?"
|
|
1390
|
+
command. Composes existing diagnostics into a one-screen OK / warn / fail report:
|
|
1391
|
+
install root, registered domain(s), skill registry, hook wiring, Codex surface-adapter
|
|
1392
|
+
status, USER.md placeholder check, and pending-draft count.
|
|
1393
|
+
|
|
1394
|
+
It writes nothing to the install and emits no events (the install is byte-identical
|
|
1395
|
+
after a run). It reports; it does not fix.
|
|
1396
|
+
|
|
1397
|
+
Exit is non-zero only on a FAIL check (no/invalid/unreadable install, an invalid skill
|
|
1398
|
+
registry). WARN checks (incomplete hook wiring, a non-compliant Codex adapter, USER.md
|
|
1399
|
+
placeholders, pending drafts) are informational and never fail the command.
|
|
1400
|
+
|
|
1401
|
+
Options:
|
|
1402
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1403
|
+
--json Emit the machine-readable report: { root, checks, summary, exit }
|
|
1404
|
+
`.trim(),
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function showDueHelp() {
|
|
1409
|
+
console.log(
|
|
1410
|
+
`
|
|
1411
|
+
Usage: phronesis due [--json] [--dir <install>]
|
|
1412
|
+
phronesis due complete <schedule_id> [--dir <install>]
|
|
1413
|
+
phronesis due run <schedule_id> [--dir <install>]
|
|
1414
|
+
|
|
1415
|
+
The V1 cadence primitive (change 0042). Recurring work is declared in the schedule
|
|
1416
|
+
registry (.phronesis/schedule.json) — one typed entry per recurrence (id / kind /
|
|
1417
|
+
target / cadence). \`phronesis due\` is a PURE READ: it computes overdue entries from the
|
|
1418
|
+
registry + the clock and prints them. It emits nothing and writes nothing — the
|
|
1419
|
+
\`schedule.due\` events are the session-start due-check's job (phronesis hooks due-check).
|
|
1420
|
+
|
|
1421
|
+
\`phronesis due complete <id>\` is the explicit completion floor: it advances the entry's
|
|
1422
|
+
last-completed date so it stops surfacing until its next cadence boundary. (A domain-
|
|
1423
|
+
agnostic \`kind: skill\` entry also completes automatically when its target skill runs; a
|
|
1424
|
+
domain-scoped one is confirmed with \`due complete\`, since a skill completion carries no domain.)
|
|
1425
|
+
|
|
1426
|
+
\`phronesis due run <id>\` is the operator-invoked one-key executor for deterministic
|
|
1427
|
+
\`cli_verb\` and \`connector_pull\` entries. \`kind: skill\` is refused; run the skill in the
|
|
1428
|
+
operator session, then \`due complete <id>\`. A successful run advances through the same
|
|
1429
|
+
audit-first completion primitive as \`due complete\`; a failing target leaves the schedule due.
|
|
1430
|
+
|
|
1431
|
+
Nothing here ever invokes a model — a schedule only surfaces or runs deterministic work under
|
|
1432
|
+
operator presence.
|
|
1433
|
+
|
|
1434
|
+
Options:
|
|
1435
|
+
--json Emit the overdue list as JSON: [{ schedule_id, target, kind, days_overdue }]
|
|
1436
|
+
--dir <install> Installation root (default: walk up to .phronesis/registry.json)
|
|
1437
|
+
`.trim(),
|
|
1438
|
+
);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function showIndexHelp() {
|
|
1442
|
+
console.log(
|
|
1443
|
+
`
|
|
1444
|
+
Usage: phronesis index rebuild [--json] [--dir <install>]
|
|
1445
|
+
|
|
1446
|
+
Regenerates the derived machine search index (.phronesis/index/) from domains/*/compiled/
|
|
1447
|
+
and the shared codex/ (change 0008: the machine index is derived, gitignored, and
|
|
1448
|
+
rebuildable — never source of truth). Losing it never loses data; search/query also
|
|
1449
|
+
self-repair the index on every load, so rebuild is the explicit full-regeneration verb.
|
|
1450
|
+
`.trim(),
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function showEventsHelp() {
|
|
1455
|
+
console.log(
|
|
1456
|
+
`
|
|
1457
|
+
Usage:
|
|
1458
|
+
phronesis events purge --before YYYY-MM-DD [--archive] [--dir <install>]
|
|
1459
|
+
phronesis events rotate [--dir <install>]
|
|
1460
|
+
|
|
1461
|
+
Maintain the local event stream (specs/event-log.md). Automatic scheduling is the daemon's
|
|
1462
|
+
job (T4.1); these are the explicitly-invokable mechanisms. Both verbs ALSO apply the same
|
|
1463
|
+
boundary to the hook audit log (.phronesis/audit.log) — one retention story, one verb family
|
|
1464
|
+
(core-0027). The audit log is a single append file, so it is filtered line-by-line: rotated
|
|
1465
|
+
or purged lines past the boundary move to .phronesis/audit.log.archive (or drop on a plain
|
|
1466
|
+
purge).
|
|
1467
|
+
|
|
1468
|
+
Run these when no session is actively writing the install (the standing one-writer-per-
|
|
1469
|
+
installation model — see actions.js). The audit-log filter rewrites the single live file in
|
|
1470
|
+
place, so an audit line appended by a concurrent hook fire DURING the rewrite can be lost; the
|
|
1471
|
+
daemon serializes this at V1.5. The day-file event stream is unaffected (only inactive past
|
|
1472
|
+
days are touched).
|
|
1473
|
+
|
|
1474
|
+
purge: DELETE day files before --before (operator wipe / privacy). --archive moves them to
|
|
1475
|
+
.phronesis/events/archive/ instead of deleting. Audit lines before --before are
|
|
1476
|
+
dropped too (or archived with --archive).
|
|
1477
|
+
rotate: ARCHIVE day files older than event_log.retention_days (default 90) to archive/, and
|
|
1478
|
+
audit lines past the same window to audit.log.archive.
|
|
1479
|
+
`.trim(),
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
async function run(argv) {
|
|
1484
|
+
const parsed = parseArgs(argv);
|
|
1485
|
+
|
|
1486
|
+
switch (parsed.command) {
|
|
1487
|
+
case 'help':
|
|
1488
|
+
showHelp();
|
|
1489
|
+
break;
|
|
1490
|
+
case 'init-help':
|
|
1491
|
+
showInitHelp();
|
|
1492
|
+
break;
|
|
1493
|
+
case 'export-help':
|
|
1494
|
+
showExportHelp();
|
|
1495
|
+
break;
|
|
1496
|
+
case 'validate-help':
|
|
1497
|
+
showValidateHelp();
|
|
1498
|
+
break;
|
|
1499
|
+
case 'links-help':
|
|
1500
|
+
showLinksHelp();
|
|
1501
|
+
break;
|
|
1502
|
+
case 'event-help':
|
|
1503
|
+
showEventHelp();
|
|
1504
|
+
break;
|
|
1505
|
+
case 'events-help':
|
|
1506
|
+
showEventsHelp();
|
|
1507
|
+
break;
|
|
1508
|
+
case 'act-help':
|
|
1509
|
+
showActHelp();
|
|
1510
|
+
break;
|
|
1511
|
+
case 'ingest-help':
|
|
1512
|
+
showIngestHelp();
|
|
1513
|
+
break;
|
|
1514
|
+
case 'compile-help':
|
|
1515
|
+
showCompileHelp();
|
|
1516
|
+
break;
|
|
1517
|
+
case 'active-context-help':
|
|
1518
|
+
showActiveContextHelp();
|
|
1519
|
+
break;
|
|
1520
|
+
case 'registry-help':
|
|
1521
|
+
showRegistryHelp();
|
|
1522
|
+
break;
|
|
1523
|
+
case 'skills-help':
|
|
1524
|
+
showSkillsHelp();
|
|
1525
|
+
break;
|
|
1526
|
+
case 'eval-help':
|
|
1527
|
+
showEvalHelp();
|
|
1528
|
+
break;
|
|
1529
|
+
case 'hooks-help':
|
|
1530
|
+
showHooksHelp();
|
|
1531
|
+
break;
|
|
1532
|
+
case 'adapter-help':
|
|
1533
|
+
showAdapterHelp();
|
|
1534
|
+
break;
|
|
1535
|
+
case 'guard-help':
|
|
1536
|
+
showGuardHelp();
|
|
1537
|
+
break;
|
|
1538
|
+
case 'search-help':
|
|
1539
|
+
showSearchHelp();
|
|
1540
|
+
break;
|
|
1541
|
+
case 'query-help':
|
|
1542
|
+
showQueryHelp();
|
|
1543
|
+
break;
|
|
1544
|
+
case 'lint-help':
|
|
1545
|
+
showLintHelp();
|
|
1546
|
+
break;
|
|
1547
|
+
case 'doctor-help':
|
|
1548
|
+
showDoctorHelp();
|
|
1549
|
+
break;
|
|
1550
|
+
case 'due-help':
|
|
1551
|
+
showDueHelp();
|
|
1552
|
+
break;
|
|
1553
|
+
case 'index-help':
|
|
1554
|
+
showIndexHelp();
|
|
1555
|
+
break;
|
|
1556
|
+
case 'version':
|
|
1557
|
+
console.log(version);
|
|
1558
|
+
break;
|
|
1559
|
+
case 'init': {
|
|
1560
|
+
const { init } = await import('./init.js');
|
|
1561
|
+
await init({
|
|
1562
|
+
dir: parsed.dir,
|
|
1563
|
+
force: parsed.force,
|
|
1564
|
+
domain: parsed.domain,
|
|
1565
|
+
name: parsed.name,
|
|
1566
|
+
role: parsed.role,
|
|
1567
|
+
company: parsed.company,
|
|
1568
|
+
workingStyle: parsed.workingStyle,
|
|
1569
|
+
});
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
case 'export': {
|
|
1573
|
+
const { exportInstallation } = await import('./export.js');
|
|
1574
|
+
await exportInstallation({ dir: parsed.dir, out: parsed.out });
|
|
1575
|
+
break;
|
|
1576
|
+
}
|
|
1577
|
+
case 'validate': {
|
|
1578
|
+
const { validateInstallation } = await import('./validate.js');
|
|
1579
|
+
await validateInstallation({ dir: parsed.dir });
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
case 'links': {
|
|
1583
|
+
const { runLinks } = await import('./links.js');
|
|
1584
|
+
await runLinks({
|
|
1585
|
+
dir: parsed.dir,
|
|
1586
|
+
object: parsed.object,
|
|
1587
|
+
hops: parsed.hops,
|
|
1588
|
+
direction: parsed.direction,
|
|
1589
|
+
json: parsed.json,
|
|
1590
|
+
});
|
|
1591
|
+
break;
|
|
1592
|
+
}
|
|
1593
|
+
case 'event-emit': {
|
|
1594
|
+
const { runEventEmit } = await import('./event.js');
|
|
1595
|
+
await runEventEmit({
|
|
1596
|
+
type: parsed.type,
|
|
1597
|
+
session: parsed.session,
|
|
1598
|
+
payload: parsed.payload,
|
|
1599
|
+
actor: parsed.actor,
|
|
1600
|
+
surface: parsed.surface,
|
|
1601
|
+
dir: parsed.dir,
|
|
1602
|
+
invocationId: parsed.invocationId,
|
|
1603
|
+
});
|
|
1604
|
+
break;
|
|
1605
|
+
}
|
|
1606
|
+
case 'event-tail': {
|
|
1607
|
+
const { runEventTail } = await import('./event.js');
|
|
1608
|
+
await runEventTail({
|
|
1609
|
+
type: parsed.type,
|
|
1610
|
+
since: parsed.since,
|
|
1611
|
+
follow: parsed.follow,
|
|
1612
|
+
dir: parsed.dir,
|
|
1613
|
+
});
|
|
1614
|
+
break;
|
|
1615
|
+
}
|
|
1616
|
+
case 'events-purge': {
|
|
1617
|
+
const { runEventsPurge } = await import('./event.js');
|
|
1618
|
+
await runEventsPurge({ before: parsed.before, archive: parsed.archive, dir: parsed.dir });
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
case 'events-rotate': {
|
|
1622
|
+
const { runEventsRotate } = await import('./event.js');
|
|
1623
|
+
await runEventsRotate({ dir: parsed.dir });
|
|
1624
|
+
break;
|
|
1625
|
+
}
|
|
1626
|
+
case 'act': {
|
|
1627
|
+
const { runAct } = await import('./act.js');
|
|
1628
|
+
await runAct({
|
|
1629
|
+
type: parsed.type,
|
|
1630
|
+
json: parsed.json,
|
|
1631
|
+
session: parsed.session,
|
|
1632
|
+
domain: parsed.domain,
|
|
1633
|
+
actor: parsed.actor,
|
|
1634
|
+
surface: parsed.surface,
|
|
1635
|
+
dir: parsed.dir,
|
|
1636
|
+
invocationId: parsed.invocationId,
|
|
1637
|
+
});
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1640
|
+
case 'ingest': {
|
|
1641
|
+
const { runIngest } = await import('./ingest.js');
|
|
1642
|
+
await runIngest({
|
|
1643
|
+
source: parsed.source,
|
|
1644
|
+
paste: parsed.paste,
|
|
1645
|
+
trust: parsed.trust,
|
|
1646
|
+
lane: parsed.lane,
|
|
1647
|
+
domain: parsed.domain,
|
|
1648
|
+
dir: parsed.dir,
|
|
1649
|
+
json: parsed.json,
|
|
1650
|
+
sourceConnector: parsed.sourceConnector,
|
|
1651
|
+
sourceExternalId: parsed.sourceExternalId,
|
|
1652
|
+
sourceTimestamp: parsed.sourceTimestamp,
|
|
1653
|
+
title: parsed.title,
|
|
1654
|
+
slug: parsed.slug,
|
|
1655
|
+
});
|
|
1656
|
+
break;
|
|
1657
|
+
}
|
|
1658
|
+
case 'compile-accept': {
|
|
1659
|
+
const { runCompileAccept } = await import('./compile.js');
|
|
1660
|
+
await runCompileAccept({
|
|
1661
|
+
draftPath: parsed.draftPath,
|
|
1662
|
+
session: parsed.session,
|
|
1663
|
+
surface: parsed.surface,
|
|
1664
|
+
dir: parsed.dir,
|
|
1665
|
+
});
|
|
1666
|
+
break;
|
|
1667
|
+
}
|
|
1668
|
+
case 'compile-reject': {
|
|
1669
|
+
const { runCompileReject } = await import('./compile.js');
|
|
1670
|
+
await runCompileReject({
|
|
1671
|
+
draftPath: parsed.draftPath,
|
|
1672
|
+
reason: parsed.reason,
|
|
1673
|
+
session: parsed.session,
|
|
1674
|
+
surface: parsed.surface,
|
|
1675
|
+
dir: parsed.dir,
|
|
1676
|
+
});
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
case 'compile-review': {
|
|
1680
|
+
const { runCompileReview } = await import('./compile.js');
|
|
1681
|
+
await runCompileReview({
|
|
1682
|
+
domain: parsed.domain,
|
|
1683
|
+
session: parsed.session,
|
|
1684
|
+
surface: parsed.surface,
|
|
1685
|
+
dir: parsed.dir,
|
|
1686
|
+
rationale: parsed.rationale,
|
|
1687
|
+
approveAll: parsed.approveAll,
|
|
1688
|
+
resume: parsed.resume,
|
|
1689
|
+
json: parsed.json,
|
|
1690
|
+
});
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
case 'active-context-refresh': {
|
|
1694
|
+
const { runActiveContextRefresh } = await import('./active-context.js');
|
|
1695
|
+
await runActiveContextRefresh({ dir: parsed.dir, domain: parsed.domain, json: parsed.json });
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
case 'registry-status': {
|
|
1699
|
+
const { runRegistryStatus } = await import('./registry.js');
|
|
1700
|
+
await runRegistryStatus({ dir: parsed.dir, json: parsed.json });
|
|
1701
|
+
break;
|
|
1702
|
+
}
|
|
1703
|
+
case 'skills-status': {
|
|
1704
|
+
const { runSkillsStatus } = await import('./skills.js');
|
|
1705
|
+
await runSkillsStatus({ dir: parsed.dir, json: parsed.json });
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
case 'eval-view': {
|
|
1709
|
+
const { runEvalView } = await import('./eval.js');
|
|
1710
|
+
await runEvalView({
|
|
1711
|
+
repo: parsed.repo,
|
|
1712
|
+
skill: parsed.skill,
|
|
1713
|
+
goldensDir: parsed.goldensDir,
|
|
1714
|
+
port: parsed.port,
|
|
1715
|
+
host: parsed.host,
|
|
1716
|
+
json: parsed.json,
|
|
1717
|
+
serve: parsed.serve,
|
|
1718
|
+
});
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
case 'skills-bump': {
|
|
1722
|
+
const { runSkillsBump } = await import('./skills.js');
|
|
1723
|
+
await runSkillsBump({
|
|
1724
|
+
skill: parsed.skill,
|
|
1725
|
+
to: parsed.to,
|
|
1726
|
+
repo: parsed.repo,
|
|
1727
|
+
k: parsed.k,
|
|
1728
|
+
acceptRegression: parsed.acceptRegression,
|
|
1729
|
+
rebaseline: parsed.rebaseline,
|
|
1730
|
+
shapeChange: parsed.shapeChange,
|
|
1731
|
+
qualityFloor: parsed.qualityFloor,
|
|
1732
|
+
coverageFloor: parsed.coverageFloor,
|
|
1733
|
+
rationale: parsed.rationale,
|
|
1734
|
+
acceptedBy: parsed.acceptedBy,
|
|
1735
|
+
judgeModel: parsed.judgeModel,
|
|
1736
|
+
goldensDir: parsed.goldensDir,
|
|
1737
|
+
domain: parsed.domain,
|
|
1738
|
+
json: parsed.json,
|
|
1739
|
+
});
|
|
1740
|
+
break;
|
|
1741
|
+
}
|
|
1742
|
+
case 'hooks-due-check': {
|
|
1743
|
+
const { runHooksDueCheck } = await import('./hooks.js');
|
|
1744
|
+
await runHooksDueCheck({
|
|
1745
|
+
session: parsed.session,
|
|
1746
|
+
dir: parsed.dir,
|
|
1747
|
+
actor: parsed.actor,
|
|
1748
|
+
surface: parsed.surface,
|
|
1749
|
+
});
|
|
1750
|
+
break;
|
|
1751
|
+
}
|
|
1752
|
+
case 'hooks-sweep-status': {
|
|
1753
|
+
const { runHooksSweepStatus } = await import('./hooks.js');
|
|
1754
|
+
await runHooksSweepStatus({ session: parsed.session, dir: parsed.dir, json: parsed.json });
|
|
1755
|
+
break;
|
|
1756
|
+
}
|
|
1757
|
+
case 'hooks-skill-start': {
|
|
1758
|
+
const { runHooksSkillStart } = await import('./hooks.js');
|
|
1759
|
+
await runHooksSkillStart({ dir: parsed.dir, payload: parsed.payload });
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
case 'hooks-skill-complete': {
|
|
1763
|
+
const { runHooksSkillComplete } = await import('./hooks.js');
|
|
1764
|
+
await runHooksSkillComplete({
|
|
1765
|
+
session: parsed.session,
|
|
1766
|
+
dir: parsed.dir,
|
|
1767
|
+
payload: parsed.payload,
|
|
1768
|
+
boundaryEvent: parsed.boundaryEvent,
|
|
1769
|
+
json: parsed.json,
|
|
1770
|
+
});
|
|
1771
|
+
break;
|
|
1772
|
+
}
|
|
1773
|
+
case 'hooks-skill-abandon': {
|
|
1774
|
+
const { runHooksSkillAbandon } = await import('./hooks.js');
|
|
1775
|
+
await runHooksSkillAbandon({ dir: parsed.dir, payload: parsed.payload, json: parsed.json });
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
case 'hooks-skill-sweep-stale': {
|
|
1779
|
+
const { runHooksSkillSweepStale } = await import('./hooks.js');
|
|
1780
|
+
await runHooksSkillSweepStale({ session: parsed.session, dir: parsed.dir, json: parsed.json });
|
|
1781
|
+
break;
|
|
1782
|
+
}
|
|
1783
|
+
case 'adapter-test': {
|
|
1784
|
+
const { runAdapterTest } = await import('./adapter.js');
|
|
1785
|
+
await runAdapterTest({
|
|
1786
|
+
surface: parsed.surface,
|
|
1787
|
+
dir: parsed.dir,
|
|
1788
|
+
domain: parsed.domain,
|
|
1789
|
+
json: parsed.json,
|
|
1790
|
+
});
|
|
1791
|
+
break;
|
|
1792
|
+
}
|
|
1793
|
+
case 'guard': {
|
|
1794
|
+
const { runGuard } = await import('./guard.js');
|
|
1795
|
+
await runGuard({
|
|
1796
|
+
dir: parsed.dir,
|
|
1797
|
+
session: parsed.session,
|
|
1798
|
+
surface: parsed.surface,
|
|
1799
|
+
actor: parsed.actor,
|
|
1800
|
+
domain: parsed.domain,
|
|
1801
|
+
json: parsed.json,
|
|
1802
|
+
});
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
case 'search': {
|
|
1806
|
+
const { runSearch } = await import('./search.js');
|
|
1807
|
+
await runSearch({
|
|
1808
|
+
query: parsed.query,
|
|
1809
|
+
dir: parsed.dir,
|
|
1810
|
+
domain: parsed.domain,
|
|
1811
|
+
allDomains: parsed.allDomains,
|
|
1812
|
+
includeArtifacts: parsed.includeArtifacts,
|
|
1813
|
+
limit: parsed.limit,
|
|
1814
|
+
json: parsed.json,
|
|
1815
|
+
});
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
case 'query': {
|
|
1819
|
+
const { runQuery } = await import('./search.js');
|
|
1820
|
+
await runQuery({
|
|
1821
|
+
query: parsed.query,
|
|
1822
|
+
dir: parsed.dir,
|
|
1823
|
+
domain: parsed.domain,
|
|
1824
|
+
allDomains: parsed.allDomains,
|
|
1825
|
+
includeArtifacts: parsed.includeArtifacts,
|
|
1826
|
+
limit: parsed.limit,
|
|
1827
|
+
hops: parsed.hops,
|
|
1828
|
+
json: parsed.json,
|
|
1829
|
+
});
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
case 'lint': {
|
|
1833
|
+
const { runLint } = await import('./lint.js');
|
|
1834
|
+
await runLint({
|
|
1835
|
+
dir: parsed.dir,
|
|
1836
|
+
domain: parsed.domain,
|
|
1837
|
+
allDomains: parsed.allDomains,
|
|
1838
|
+
staleMonths: parsed.staleMonths,
|
|
1839
|
+
json: parsed.json,
|
|
1840
|
+
});
|
|
1841
|
+
break;
|
|
1842
|
+
}
|
|
1843
|
+
case 'doctor': {
|
|
1844
|
+
const { runDoctor } = await import('./doctor.js');
|
|
1845
|
+
await runDoctor({ dir: parsed.dir, json: parsed.json });
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
case 'due': {
|
|
1849
|
+
const { runDue } = await import('./due.js');
|
|
1850
|
+
await runDue({ dir: parsed.dir, json: parsed.json });
|
|
1851
|
+
break;
|
|
1852
|
+
}
|
|
1853
|
+
case 'due-complete': {
|
|
1854
|
+
const { runDueComplete } = await import('./due.js');
|
|
1855
|
+
await runDueComplete({ dir: parsed.dir, scheduleId: parsed.scheduleId });
|
|
1856
|
+
break;
|
|
1857
|
+
}
|
|
1858
|
+
case 'due-run': {
|
|
1859
|
+
const { runDueRun } = await import('./due.js');
|
|
1860
|
+
await runDueRun({ dir: parsed.dir, scheduleId: parsed.scheduleId });
|
|
1861
|
+
break;
|
|
1862
|
+
}
|
|
1863
|
+
case 'index-rebuild': {
|
|
1864
|
+
const { runIndexRebuild } = await import('./search.js');
|
|
1865
|
+
await runIndexRebuild({ dir: parsed.dir, json: parsed.json });
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
case 'invalid':
|
|
1869
|
+
console.error(parsed.error);
|
|
1870
|
+
console.log();
|
|
1871
|
+
showHelp();
|
|
1872
|
+
process.exitCode = 1;
|
|
1873
|
+
break;
|
|
1874
|
+
case 'unknown':
|
|
1875
|
+
console.error(`Unknown command: ${parsed.input}`);
|
|
1876
|
+
console.log();
|
|
1877
|
+
showHelp();
|
|
1878
|
+
process.exitCode = 1;
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
export { parseArgs, run, showHelp, showInitHelp };
|
|
1884
|
+
|
|
1885
|
+
if (process.argv[1]) {
|
|
1886
|
+
try {
|
|
1887
|
+
const arg1Real = realpathSync(resolve(process.argv[1]));
|
|
1888
|
+
const selfReal = fileURLToPath(import.meta.url);
|
|
1889
|
+
if (arg1Real === selfReal) run();
|
|
1890
|
+
} catch {
|
|
1891
|
+
// realpathSync can fail if the path doesn't exist; skip silently
|
|
1892
|
+
}
|
|
1893
|
+
}
|