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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Action-writer helpers — the mechanical half of the T2.2 action spine (core-0008).
|
|
2
|
+
// Everything here is shared by every deterministic action type so core-0009/0010 are
|
|
3
|
+
// "fill handlers + schemas + gate rows": slug derivation, frontmatter serialization,
|
|
4
|
+
// the D12 dated-append-block shape, and the stage→validate→rename single-dest writer
|
|
5
|
+
// (rules.md:167 — no output artifact is ever half-written). No business logic: which
|
|
6
|
+
// fields an action writes is the handler's job (actions.js); how bytes land is ours.
|
|
7
|
+
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
import fse from 'fs-extra';
|
|
12
|
+
import { checkObjectContent } from './validate-object.js';
|
|
13
|
+
import { matchFrontmatterBlock } from './workspace-scan.js';
|
|
14
|
+
|
|
15
|
+
// Hand-rolled zero-dep slugify → kebab ASCII matching the contract's slug grammar
|
|
16
|
+
// (contract.js RE_SLUG). Deliberately NOT the npm `slugify` package — that stays an
|
|
17
|
+
// apps/cli init-only dep; @phronesis/core holds its deps to js-yaml + fs-extra.
|
|
18
|
+
// Diacritics fold to their base letter (NFKD strip); anything else non-alphanumeric
|
|
19
|
+
// collapses to a single hyphen. Returns '' when nothing survives — callers fail closed.
|
|
20
|
+
export function slugify(input) {
|
|
21
|
+
if (typeof input !== 'string') return '';
|
|
22
|
+
return input
|
|
23
|
+
.normalize('NFKD')
|
|
24
|
+
.replace(/[\u0300-\u036f]/g, '') // combining marks left by NFKD
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
27
|
+
.replace(/^-+|-+$/g, '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Serialize parsed frontmatter back to a `---` block. JSON_SCHEMA mirrors how the
|
|
31
|
+
// validator *reads* (validate.js / link-index.js): no implicit type coercion, so a
|
|
32
|
+
// `2026-05-15` date stays the plain string it was given. Insertion order preserved.
|
|
33
|
+
export function serializeFrontmatter(fm) {
|
|
34
|
+
const body = yaml.dump(fm, { schema: yaml.JSON_SCHEMA, lineWidth: -1, noRefs: true });
|
|
35
|
+
return `---\n${body}---\n`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- D12 — the named dated-append-block format ---------------------------------------
|
|
39
|
+
//
|
|
40
|
+
// ONE shared shape for every append action (`enrich_person` here; project-state and
|
|
41
|
+
// changelog appends in core-0009), so the writers never improvise differently and later
|
|
42
|
+
// lint/retrieval can rely on it. V1 shape, frozen in core-0008: a stable `## Log`
|
|
43
|
+
// section; each entry `### YYYY-MM-DD\n\n{body}\n`, newest appended at the section end.
|
|
44
|
+
// Producer-local for V1 — it graduates to a spec note only if T5.2/T5.3 parse it.
|
|
45
|
+
//
|
|
46
|
+
// KNOWN LIMITS (line-based): an entry body whose own line starts `## ` reads as a new
|
|
47
|
+
// H2 and ends the Log section for every later append; and a payload-supplied backdate
|
|
48
|
+
// lands at the section end regardless, so "newest last" holds only for stamp-dated
|
|
49
|
+
// entries. Both acceptable while the shape is producer-local; revisit if it graduates.
|
|
50
|
+
|
|
51
|
+
const LOG_HEADING = '## Log';
|
|
52
|
+
|
|
53
|
+
// Split an object's source into its frontmatter block and its body, BYTE-precisely —
|
|
54
|
+
// the read-modify-write actions (record_outcome, update_project_state's headline path,
|
|
55
|
+
// core-0010's tombstone) rewrite the frontmatter and must carry the body over verbatim,
|
|
56
|
+
// including any leading blank line. splitFrontmatter (workspace-scan) returns only the
|
|
57
|
+
// YAML source, so it cannot say where the body starts; this one can. Both ride the ONE
|
|
58
|
+
// shared block regex (matchFrontmatterBlock) — the validator and the writers must never
|
|
59
|
+
// disagree about where a frontmatter block ends. Returns null when the content has no
|
|
60
|
+
// frontmatter block.
|
|
61
|
+
export function splitObjectSource(content) {
|
|
62
|
+
const m = matchFrontmatterBlock(content);
|
|
63
|
+
if (!m) return null;
|
|
64
|
+
return { yamlSource: m[1], body: content.slice(m[0].length) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatLogEntry(dateStamp, body) {
|
|
68
|
+
return `### ${dateStamp}\n\n${String(body).trim()}\n`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Append one dated entry to the content's `## Log` section, creating the section at the
|
|
72
|
+
// end of the document if absent. Everything outside the inserted lines is preserved
|
|
73
|
+
// verbatim — prior bytes are never reflowed.
|
|
74
|
+
export function appendLogBlock(content, dateStamp, body) {
|
|
75
|
+
const entry = formatLogEntry(dateStamp, body);
|
|
76
|
+
const lines = content.split('\n');
|
|
77
|
+
const headingAt = lines.findIndex((l) => l.trimEnd() === LOG_HEADING);
|
|
78
|
+
|
|
79
|
+
if (headingAt === -1) {
|
|
80
|
+
const sep = content.endsWith('\n\n') ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
81
|
+
return `${content}${sep}${LOG_HEADING}\n\n${entry}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Section end = just before the next H2, else end of document.
|
|
85
|
+
let insertAt = lines.length;
|
|
86
|
+
for (let i = headingAt + 1; i < lines.length; i += 1) {
|
|
87
|
+
if (/^##\s/.test(lines[i]) && !/^###/.test(lines[i])) {
|
|
88
|
+
insertAt = i;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Trim trailing blank lines inside the section so the entry lands flush, one blank
|
|
93
|
+
// line after whatever the section already holds.
|
|
94
|
+
let end = insertAt;
|
|
95
|
+
while (end > headingAt + 1 && lines[end - 1].trim() === '') end -= 1;
|
|
96
|
+
const inserted = [...lines.slice(0, end), '', ...entry.split('\n')];
|
|
97
|
+
// formatLogEntry ends with '\n' → its split leaves a trailing ''; that supplies the
|
|
98
|
+
// blank line before a following H2 (or the file's final newline at EOF).
|
|
99
|
+
return [...inserted, ...lines.slice(insertAt)].join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- The stage→validate→rename writer (rules.md:167; multi-dest since core-0010) -------
|
|
103
|
+
//
|
|
104
|
+
// Write every artifact to `<dest>.partial-<pid>-<random>` (never `.md`, so workspace
|
|
105
|
+
// scans ignore it), validate ALL staged bytes against the full ontology contract —
|
|
106
|
+
// frontmatter AND link existence, exactly what `phronesis validate` enforces — and only
|
|
107
|
+
// then commit the renames, in array order. Any failure before the first rename removes
|
|
108
|
+
// every temp and renames nothing.
|
|
109
|
+
//
|
|
110
|
+
// F2 (core-0010) — all-or-nothing INTENT, idempotent-convergence guarantee: true 2-phase
|
|
111
|
+
// commit is not achievable on a POSIX fs without a journal, so a failure BETWEEN renames
|
|
112
|
+
// leaves the earlier writes committed. Callers therefore order `writes` so the
|
|
113
|
+
// recoverable direction commits first (promote_to_codex: codex object before the source
|
|
114
|
+
// tombstone) and make a re-run converge: `skipIfIdentical` treats an existing
|
|
115
|
+
// destination with byte-identical content as already-committed (a no-op), so the re-run
|
|
116
|
+
// completes the remaining writes instead of failing on collision.
|
|
117
|
+
//
|
|
118
|
+
// Validation sees the POST-transaction state: every write's relPath counts as existing
|
|
119
|
+
// for sibling link checks (the tombstone's `superseded-by` link resolves against the
|
|
120
|
+
// in-flight codex object). Callers must order writes so link dependencies commit first.
|
|
121
|
+
//
|
|
122
|
+
// `overwrite: false` also makes the final rename itself refuse a destination that
|
|
123
|
+
// appeared after the pre-flight check — the no-overwrite policy holds across a race.
|
|
124
|
+
export async function stageValidateRenameAll({ installRoot, writes }) {
|
|
125
|
+
// The path a write is VALIDATED as (its canonical typed-object shape) can differ from
|
|
126
|
+
// where it is WRITTEN: a compile draft is validated as its canonical compiled/ path —
|
|
127
|
+
// so contextFor derives the right layer/type-dir context and checkObjectContent
|
|
128
|
+
// accepts it — but written atomically to .phronesis/drafts/ (core-0017 H3). A
|
|
129
|
+
// `.phronesis/drafts/…` write path is not a typed-object path, so validating it as
|
|
130
|
+
// itself would (correctly) fail "not a typed-object path"; validateRelPath is the fix.
|
|
131
|
+
// It defaults to relPath, so the six deterministic writers are unchanged (write path
|
|
132
|
+
// == validate path) and every link/sibling check sees exactly the same input.
|
|
133
|
+
const validatePathOf = (w) => w.validateRelPath || w.relPath;
|
|
134
|
+
const pending = new Set(writes.map(validatePathOf));
|
|
135
|
+
const staged = []; // { write, tmp } — tmp null when the write is an identical no-op
|
|
136
|
+
try {
|
|
137
|
+
for (const w of writes) {
|
|
138
|
+
const dest = join(installRoot, w.relPath);
|
|
139
|
+
if (!w.overwrite && (await fse.pathExists(dest))) {
|
|
140
|
+
if (w.skipIfIdentical && (await readFile(dest, 'utf8')) === w.content) {
|
|
141
|
+
staged.push({ write: w, tmp: null });
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(
|
|
145
|
+
`${w.relPath} already exists with different content — fail closed (never overwrite; resolve the destination out-of-band)`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
// pid + random: pid alone collides for two concurrent runAction calls in ONE
|
|
149
|
+
// process (the T4.1 daemon and adapter surfaces are the declared next callers) —
|
|
150
|
+
// one caller could validate and rename the other's bytes.
|
|
151
|
+
const tmp = `${dest}.partial-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
152
|
+
staged.push({ write: w, tmp });
|
|
153
|
+
await fse.ensureDir(dirname(dest));
|
|
154
|
+
await writeFile(tmp, w.content);
|
|
155
|
+
const stagedBytes = await readFile(tmp, 'utf8');
|
|
156
|
+
const errors = await checkObjectContent({
|
|
157
|
+
installRoot,
|
|
158
|
+
relPath: validatePathOf(w),
|
|
159
|
+
content: stagedBytes,
|
|
160
|
+
pending,
|
|
161
|
+
});
|
|
162
|
+
if (errors.length) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`staged artifact fails the ontology contract (${validatePathOf(w)}): ${errors.join('; ')}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const s of staged) {
|
|
169
|
+
if (s.tmp === null) continue;
|
|
170
|
+
await fse.move(s.tmp, join(installRoot, s.write.relPath), {
|
|
171
|
+
overwrite: s.write.overwrite === true,
|
|
172
|
+
});
|
|
173
|
+
s.tmp = null; // consumed by the rename — nothing to clean up
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
// allSettled: a failing cleanup (EACCES on a temp) must never replace the real
|
|
177
|
+
// error the caller needs. KNOWN LIMIT: a SIGKILL between renames strands the
|
|
178
|
+
// not-yet-consumed temps — `.partial-*` is scan-ignored and gitignored, so they
|
|
179
|
+
// are litter, not corruption; nothing sweeps them in V1.
|
|
180
|
+
await Promise.allSettled(staged.filter((s) => s.tmp).map((s) => fse.remove(s.tmp)));
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The single-destination writer every single-file action rides — one write, same
|
|
186
|
+
// staging, validation, and cleanup semantics (one implementation, not a sibling copy).
|
|
187
|
+
// `validateRelPath` (optional) is the canonical path the bytes are validated as when it
|
|
188
|
+
// differs from where they are written (the compile draft case, core-0017 H3); omit it
|
|
189
|
+
// and the write path is its own validate path (every other caller).
|
|
190
|
+
export async function stageValidateRename({
|
|
191
|
+
installRoot,
|
|
192
|
+
relPath,
|
|
193
|
+
content,
|
|
194
|
+
overwrite = false,
|
|
195
|
+
validateRelPath,
|
|
196
|
+
}) {
|
|
197
|
+
await stageValidateRenameAll({
|
|
198
|
+
installRoot,
|
|
199
|
+
writes: [{ relPath, content, overwrite, validateRelPath }],
|
|
200
|
+
});
|
|
201
|
+
}
|