phronesis 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/act.js +62 -1
  3. package/src/adapter.js +366 -61
  4. package/src/cli.js +527 -32
  5. package/src/compile.js +141 -10
  6. package/src/doctor.js +63 -2
  7. package/src/hook.js +312 -0
  8. package/src/hooks-refresh.js +1363 -0
  9. package/src/hooks.js +48 -33
  10. package/src/init.js +37 -4
  11. package/src/launcher.js +2105 -0
  12. package/src/layout.js +51 -7
  13. package/src/profile.js +169 -0
  14. package/src/prompts.js +88 -0
  15. package/src/skills-bump.js +88 -20
  16. package/src/skills.js +103 -10
  17. package/templates/codex/INDEX.md +4 -2
  18. package/templates/codex/seed/ai-practice/the-workspace-is-part-of-the-thinking.md +96 -0
  19. package/templates/codex-surface/README.md +15 -16
  20. package/templates/codex-surface/hooks/_resolve.sh +25 -24
  21. package/templates/codex-surface/hooks/recall-guard.sh +5 -30
  22. package/templates/codex-surface/hooks/session-start.sh +5 -18
  23. package/templates/codex-surface/hooks/session-sweep-precompact.sh +5 -20
  24. package/templates/codex-surface/hooks/session-sweep-subagent.sh +5 -23
  25. package/templates/codex-surface/hooks/session-sweep.sh +5 -19
  26. package/templates/launcher/phronesis +95 -0
  27. package/templates/phronesis-hooks/compile-active-context.sh +12 -38
  28. package/templates/phronesis-hooks/recall-guard.sh +12 -103
  29. package/templates/phronesis-hooks/session-start.sh +10 -88
  30. package/templates/phronesis-hooks/session-sweep.sh +16 -139
  31. package/templates/phronesis-hooks/skill-lifecycle.sh +12 -37
  32. package/templates/skills/lint/SKILL.md +7 -2
  33. package/templates/skills/onboard/SKILL.md +47 -9
  34. package/templates/skills/onboard/evals/rubric.md +5 -3
  35. package/templates/skills/prd-draft/SKILL.md +34 -26
  36. package/templates/skills/prd-draft/evals/rubric.md +1 -1
  37. package/vendor/core/src/action-registry.js +6 -3
  38. package/vendor/core/src/actions.js +120 -16
  39. package/vendor/core/src/index.js +3 -0
  40. package/vendor/core/src/lint.js +22 -5
  41. package/vendor/core/src/skill-lifecycle.js +67 -7
package/src/hooks.js CHANGED
@@ -47,39 +47,15 @@ export async function runHooksDueCheck({ session, dir, actor, surface } = {}) {
47
47
  const resolvedActor = actor || process.env.PHRONESIS_ACTOR || 'agent';
48
48
  const resolvedSurface = surface || 'cli';
49
49
  try {
50
- const { due } = await runDueCheck({ installRoot, session, actor: resolvedActor, surface: resolvedSurface });
51
- if (!due.length) {
52
- console.log('No decisions due for review.');
53
- } else {
54
- console.log(`Phronesis due-check: ${due.length} decision(s) due for review.`);
55
- for (const d of due) {
56
- const parts = d.decision_path.split('/');
57
- const domain = parts[1];
58
- const name = parts[parts.length - 1].replace(/\.md$/, '');
59
- const overdue = d.days_overdue === 0 ? 'due today' : `${d.days_overdue} day(s) overdue`;
60
- console.log(`\n ${d.decision_path} — ${overdue} (review_date ${d.review_date})`);
61
- console.log(
62
- ` record the outcome: phronesis act record_outcome --domain ${domain} --session ${session} --json '{"target":"decisions/${name}","outcome_status":"<${ENUMS.outcome_status.join('|')}>","lesson":"<what you learned>"}'`,
63
- );
64
- }
65
- }
66
- // Schedule half (change 0042): a malformed schedule.json is a SEPARATE failure domain —
67
- // it degrades to zero due entries here (validate/doctor own the fault), never breaking the
68
- // decision due-check above.
69
- const { due: scheduleDue } = await runScheduleDueCheck({ installRoot, session, actor: resolvedActor, surface: resolvedSurface });
70
- if (scheduleDue.length) {
71
- console.log(`\nPhronesis schedule: ${scheduleDue.length} item(s) due.`);
72
- for (const s of scheduleDue) {
73
- const overdue = s.days_overdue === 0 ? 'due now' : `${s.days_overdue} day(s) overdue`;
74
- console.log(`\n ${s.schedule_id} — ${s.kind}:${s.target} — ${overdue}`);
75
- if (s.kind === 'cli_verb' || s.kind === 'connector_pull') {
76
- console.log(` run now: phronesis due run ${s.schedule_id}`);
77
- }
78
- console.log(` mark done: phronesis due complete ${s.schedule_id}`);
79
- }
80
- }
50
+ const result = await collectHooksDueCheck({
51
+ installRoot,
52
+ session,
53
+ actor: resolvedActor,
54
+ surface: resolvedSurface,
55
+ });
56
+ console.log(result.output);
81
57
  process.exitCode = 0;
82
- return due;
58
+ return result.due;
83
59
  } catch (err) {
84
60
  console.error(`hooks due-check: ${err.message}`);
85
61
  process.exitCode = 1;
@@ -87,6 +63,45 @@ export async function runHooksDueCheck({ session, dir, actor, surface } = {}) {
87
63
  }
88
64
  }
89
65
 
66
+ // Composable due-check primitive for the resolved `phronesis hook session-start` interface.
67
+ // It keeps the authoritative core emitters and the long-standing operator-facing bytes in one
68
+ // place: the legacy `hooks due-check` command prints `output`, while the hook dispatcher either
69
+ // prints it raw (Claude Code) or places it in Codex's additionalContext envelope.
70
+ export async function collectHooksDueCheck({ installRoot, session, actor = 'agent', surface = 'cli' } = {}) {
71
+ const { due } = await runDueCheck({ installRoot, session, actor, surface });
72
+ // Schedule is a separate failure domain: the core primitive already degrades a malformed
73
+ // schedule registry to an empty list, leaving the decision due-check intact.
74
+ const { due: scheduleDue } = await runScheduleDueCheck({ installRoot, session, actor, surface });
75
+ const lines = [];
76
+ if (!due.length) {
77
+ lines.push('No decisions due for review.');
78
+ } else {
79
+ lines.push(`Phronesis due-check: ${due.length} decision(s) due for review.`);
80
+ for (const d of due) {
81
+ const parts = d.decision_path.split('/');
82
+ const domain = parts[1];
83
+ const name = parts[parts.length - 1].replace(/\.md$/, '');
84
+ const overdue = d.days_overdue === 0 ? 'due today' : `${d.days_overdue} day(s) overdue`;
85
+ lines.push(`\n ${d.decision_path} — ${overdue} (review_date ${d.review_date})`);
86
+ lines.push(
87
+ ` record the outcome: phronesis act record_outcome --domain ${domain} --session ${session} --json '{"target":"decisions/${name}","outcome_status":"<${ENUMS.outcome_status.join('|')}>","lesson":"<what you learned>"}'`,
88
+ );
89
+ }
90
+ }
91
+ if (scheduleDue.length) {
92
+ lines.push(`\nPhronesis schedule: ${scheduleDue.length} item(s) due.`);
93
+ for (const s of scheduleDue) {
94
+ const overdue = s.days_overdue === 0 ? 'due now' : `${s.days_overdue} day(s) overdue`;
95
+ lines.push(`\n ${s.schedule_id} — ${s.kind}:${s.target} — ${overdue}`);
96
+ if (s.kind === 'cli_verb' || s.kind === 'connector_pull') {
97
+ lines.push(` run now: phronesis due run ${s.schedule_id}`);
98
+ }
99
+ lines.push(` mark done: phronesis due complete ${s.schedule_id}`);
100
+ }
101
+ }
102
+ return { due, scheduleDue, output: lines.join('\n') };
103
+ }
104
+
90
105
  // Sweep status: has this session emitted its capture-sweep marker (`session.end` with a
91
106
  // `sweep` payload — `sweep.candidates: 0` is the honest negative)? Prints `swept` or
92
107
  // `pending`; exits 0 when swept, 1 when pending — the scaffolded Stop-hook script keys
@@ -127,7 +142,7 @@ function parseHookPayload(label, payload) {
127
142
 
128
143
  // The operator-facing copy for the J1 zero-capture warning (core-0027). The POLICY (when
129
144
  // to warn) is @phronesis/core's meshZeroWarning; this is presentation only.
130
- function meshZeroWarningMessage(w) {
145
+ export function meshZeroWarningMessage(w) {
131
146
  return (
132
147
  `Phronesis: the heavy skill "${w.skill_name}" completed with 0 mesh enrichments ` +
133
148
  `(no links_updated across ${w.action_types_committed} committed action(s)). A substantive ` +
package/src/init.js CHANGED
@@ -3,9 +3,16 @@ import fse from 'fs-extra';
3
3
  import chalk from 'chalk';
4
4
  import slugify from 'slugify';
5
5
  import { createInstallation } from './scaffold.js';
6
- import { confirmOverwrite, promptProfile } from './prompts.js';
6
+ import {
7
+ confirmOverwrite,
8
+ confirmPathAppend,
9
+ confirmPersistence,
10
+ PERSISTENCE_PROMPT,
11
+ promptProfile,
12
+ } from './prompts.js';
7
13
  import { resolveProfile } from './profile.js';
8
14
  import { DEFAULT_DOMAIN } from './layout.js';
15
+ import { runLauncherInstall } from './launcher.js';
9
16
 
10
17
  const DEFAULT_DIR = 'phronesis-installation';
11
18
 
@@ -23,7 +30,20 @@ async function isPhronesisInstall(dir) {
23
30
  return fse.pathExists(join(dir, '.phronesis', 'registry.json'));
24
31
  }
25
32
 
26
- export async function init({ dir, force = false, domain, name, role, company, workingStyle } = {}) {
33
+ export async function init({
34
+ dir,
35
+ force = false,
36
+ domain,
37
+ name,
38
+ role,
39
+ company,
40
+ workingStyle,
41
+ persist,
42
+ isTTY = Boolean(process.stdin.isTTY),
43
+ promptPersistence = confirmPersistence,
44
+ promptPath = confirmPathAppend,
45
+ installLauncher = runLauncherInstall,
46
+ } = {}) {
27
47
  const targetDir = resolve(process.cwd(), dir || DEFAULT_DIR);
28
48
  const domainSlug = slugify(domain || DEFAULT_DOMAIN, { lower: true, strict: true }) || DEFAULT_DOMAIN;
29
49
  if (domain && domainSlug !== domain) {
@@ -61,7 +81,7 @@ export async function init({ dir, force = false, domain, name, role, company, wo
61
81
  }
62
82
  // A prior Phronesis install — replacement is in-bounds, but still confirm.
63
83
  if (!force) {
64
- if (process.stdin.isTTY) {
84
+ if (isTTY) {
65
85
  const ok = await confirmOverwrite(targetDir);
66
86
  if (!ok) {
67
87
  console.log('Setup cancelled.');
@@ -90,7 +110,7 @@ export async function init({ dir, force = false, domain, name, role, company, wo
90
110
  try {
91
111
  profile = await resolveProfile({
92
112
  flags: { name, role, company, workingStyle },
93
- isTTY: Boolean(process.stdin.isTTY),
113
+ isTTY,
94
114
  prompt: promptProfile,
95
115
  });
96
116
  } catch (err) {
@@ -120,6 +140,19 @@ export async function init({ dir, force = false, domain, name, role, company, wo
120
140
  }
121
141
 
122
142
  printSuccess(result);
143
+ let shouldPersist = persist === true;
144
+ if (persist === undefined && isTTY) {
145
+ shouldPersist = await promptPersistence({ message: PERSISTENCE_PROMPT });
146
+ }
147
+ if (shouldPersist) {
148
+ try {
149
+ await installLauncher({ isTTY, promptPath });
150
+ } catch (err) {
151
+ console.error(`Command installation failed: ${err.message}`);
152
+ process.exitCode = 1;
153
+ return { ...result, persistence: { ok: false, error: err.message } };
154
+ }
155
+ }
123
156
  return result;
124
157
  }
125
158