phronesis 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/act.js +62 -1
- package/src/adapter.js +366 -61
- package/src/cli.js +527 -32
- package/src/compile.js +141 -10
- package/src/doctor.js +63 -2
- package/src/hook.js +312 -0
- package/src/hooks-refresh.js +1363 -0
- package/src/hooks.js +48 -33
- package/src/init.js +37 -4
- package/src/launcher.js +2105 -0
- package/src/layout.js +51 -7
- package/src/profile.js +169 -0
- package/src/prompts.js +88 -0
- package/src/skills-bump.js +88 -20
- package/src/skills.js +103 -10
- package/templates/codex/INDEX.md +4 -2
- package/templates/codex/seed/ai-practice/the-workspace-is-part-of-the-thinking.md +96 -0
- package/templates/codex-surface/README.md +15 -16
- package/templates/codex-surface/hooks/_resolve.sh +25 -24
- package/templates/codex-surface/hooks/recall-guard.sh +5 -30
- package/templates/codex-surface/hooks/session-start.sh +5 -18
- package/templates/codex-surface/hooks/session-sweep-precompact.sh +5 -20
- package/templates/codex-surface/hooks/session-sweep-subagent.sh +5 -23
- package/templates/codex-surface/hooks/session-sweep.sh +5 -19
- package/templates/launcher/phronesis +95 -0
- package/templates/phronesis-hooks/compile-active-context.sh +12 -38
- package/templates/phronesis-hooks/recall-guard.sh +12 -103
- package/templates/phronesis-hooks/session-start.sh +10 -88
- package/templates/phronesis-hooks/session-sweep.sh +16 -139
- package/templates/phronesis-hooks/skill-lifecycle.sh +12 -37
- package/templates/skills/lint/SKILL.md +7 -2
- package/templates/skills/onboard/SKILL.md +47 -9
- package/templates/skills/onboard/evals/rubric.md +5 -3
- package/templates/skills/prd-draft/SKILL.md +34 -26
- package/templates/skills/prd-draft/evals/rubric.md +1 -1
- package/vendor/core/src/action-registry.js +6 -3
- package/vendor/core/src/actions.js +120 -16
- package/vendor/core/src/index.js +3 -0
- package/vendor/core/src/lint.js +22 -5
- package/vendor/core/src/skill-lifecycle.js +67 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: onboard
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.3.0
|
|
4
4
|
invocation:
|
|
5
5
|
explicit:
|
|
6
6
|
- "onboard me"
|
|
@@ -133,6 +133,9 @@ phronesis ingest <file-or-dir> --domain <active-domain> --lane inbox --json
|
|
|
133
133
|
Use `--lane transcripts` only when the input is a transcript. The pass signal is
|
|
134
134
|
the `--json` report with `staged >= 1` and no unexpected failures.
|
|
135
135
|
|
|
136
|
+
When you build any `phronesis act <type>` payload later in the run, read the payload
|
|
137
|
+
shape with `phronesis act <type> --schema` instead of probing it with empty payloads.
|
|
138
|
+
|
|
136
139
|
> **Checkpoint 2 - Curation.** Name what was discovered, what the operator
|
|
137
140
|
> confirmed, what was staged, and what was deliberately left out. If nothing real
|
|
138
141
|
> is available, fall back to the fixed first-session sample rather than inventing
|
|
@@ -156,7 +159,16 @@ Because first-run material is normally untrusted, expect drafts under
|
|
|
156
159
|
`.phronesis/drafts/`. Do not dump every possible object at once, and do not silently
|
|
157
160
|
pick one. Present a **ranked anchor list with salience reasoning**: for each top
|
|
158
161
|
candidate, name why it is load-bearing, and name the lower-priority or ambiguous
|
|
159
|
-
candidates you set aside and why.
|
|
162
|
+
candidates you set aside and why.
|
|
163
|
+
|
|
164
|
+
Before asking for any accept decision, **show each top draft's content, not just its
|
|
165
|
+
title, path, or classification** (see `specs/skills.md` §"Approval requires visible
|
|
166
|
+
content"): print the draft path (clickable-to-preview in Claude Code / Codex desktop)
|
|
167
|
+
and, in a plain terminal, inline the body; show a short draft in full and a long draft
|
|
168
|
+
as a structured summary with the body available on request. Never ask the operator to
|
|
169
|
+
accept a draft they have not been shown a way to read.
|
|
170
|
+
|
|
171
|
+
Then offer the operator the choice set **accept /
|
|
160
172
|
tighten / drop / different-slice**, and act on their correction before accepting
|
|
161
173
|
anything. The operator must approve what becomes canonical.
|
|
162
174
|
|
|
@@ -176,6 +188,9 @@ operator gives one rationale. Record which accepted paths now exist in
|
|
|
176
188
|
> drafts, or `action_type.committed` for deterministic typed captures. Include the
|
|
177
189
|
> salience-review evidence: the ranked anchors you showed, the operator's
|
|
178
190
|
> accept/tighten/drop/different-slice decision, and the accepted or rejected paths.
|
|
191
|
+
> Confirm the operator was shown each draft's content (not only its title or path)
|
|
192
|
+
> before the accept decision. When you name an accepted object to the operator, use
|
|
193
|
+
> its display name, not its path slug.
|
|
179
194
|
|
|
180
195
|
## Beat 4 - The one pressing thing
|
|
181
196
|
|
|
@@ -205,13 +220,25 @@ then connect it to the freshly accepted workspace state:
|
|
|
205
220
|
> **Pass-shape:** you see why each seed matched (and any keyword-near distractor set
|
|
206
221
|
> aside), and adopt only the ones you choose, with your rationale, via `promote_to_codex`.
|
|
207
222
|
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
Open the beat by explaining what these are, in the operator's language:
|
|
224
|
+
|
|
225
|
+
> Phronesis ships with a library of starter principles - distilled judgment patterns
|
|
226
|
+
> that stay quarantined until you adopt one. Adopting copies the principle into your
|
|
227
|
+
> personal codex with your reasons attached; the original stays in the library.
|
|
228
|
+
|
|
229
|
+
The starter principles are the shipped seed library (`codex/seed/`) - external material,
|
|
230
|
+
not the operator's earned codex - and adoption is the deliberate `promote_to_codex` act
|
|
231
|
+
that copies one into `codex/personal/`. Browse the installed library through
|
|
210
232
|
`codex/INDEX.md` and read a few relevant seed articles. Before recommending, **show the
|
|
211
233
|
ranked seed candidates**: for each, say why it matched the operator's just-compiled
|
|
212
234
|
material or interview answers, name any plausible but lower-ranked distractor you set
|
|
213
|
-
aside (topic-keyword nearness is not a match), and recommend at most three.
|
|
214
|
-
|
|
235
|
+
aside (topic-keyword nearness is not a match), and recommend at most three.
|
|
236
|
+
|
|
237
|
+
Before asking the operator to adopt any seed, **show that seed's content** (see
|
|
238
|
+
`specs/skills.md` §"Approval requires visible content"): a short seed article in full, a
|
|
239
|
+
longer one as a structured summary with the body available on request, with the seed path
|
|
240
|
+
as a citation. Never ask the operator to adopt a principle they have not been shown a way
|
|
241
|
+
to read. Invite the operator to redirect to a different seed before any adoption.
|
|
215
242
|
|
|
216
243
|
For each recommended seed, ask the operator whether to adopt it and why it is theirs
|
|
217
244
|
now. Only then guide the existing operator-gated action:
|
|
@@ -231,8 +258,19 @@ fabricate a seed or copy a seed into `codex/personal/` by hand.
|
|
|
231
258
|
> **Checkpoint 5 - Close the loop.** End with the accepted compiled paths, any
|
|
232
259
|
> adopted seed paths, the one pressing thing's next step, and the next suggested
|
|
233
260
|
> skill or doc. Include the seed salience evidence: the ranked seed recommendations,
|
|
234
|
-
> the operator's rationale or redirect, the adopted paths, and any skipped seeds.
|
|
235
|
-
>
|
|
261
|
+
> the operator's rationale or redirect, the adopted paths, and any skipped seeds.
|
|
262
|
+
> Confirm each recommended seed's content was shown before adoption. Do
|
|
263
|
+
> not claim a clean workspace; first-run lint may still show expected orphans — objects
|
|
264
|
+
> not yet linked into the mesh in either direction (a freshly enriched person wired only
|
|
265
|
+
> to a raw source is the common early case).
|
|
266
|
+
>
|
|
267
|
+
> **Speak in the operator's language, not the engine's.** When you name a person,
|
|
268
|
+
> organization, or object to the operator, use its display name (for example
|
|
269
|
+
> "Matt Moss"), not its path slug ("matt-moss") or file path. Describe what changed in
|
|
270
|
+
> plain outcomes ("your workspace now holds three principles you chose"; "you added four
|
|
271
|
+
> people"), not the internal graph or ledger tallies the engine keeps for its own
|
|
272
|
+
> bookkeeping. A path may appear as evidence or a citation, but never as the
|
|
273
|
+
> operator-facing name of a thing.
|
|
236
274
|
|
|
237
275
|
## Without the runtime
|
|
238
276
|
|
|
@@ -250,7 +288,7 @@ a draft capture at
|
|
|
250
288
|
`domains/{domain}/raw/degraded/YYYY-MM-DD-onboard-{slug}.md` - never
|
|
251
289
|
`compiled/`, never `codex/`, and no events emitted or back-filled. The capture
|
|
252
290
|
frontmatter must carry `degraded: true`, `intended_action_type`, `skill_name:
|
|
253
|
-
onboard`, `skill_version: 0.
|
|
291
|
+
onboard`, `skill_version: 0.3.0`, `created`, `trust_tier: untrusted`, the exact
|
|
254
292
|
payload that would have gone to the action type, and the operator approval state.
|
|
255
293
|
|
|
256
294
|
Seed adoption cannot be performed runtime-free. Capture the operator's adoption
|
|
@@ -13,7 +13,7 @@ skill. The criteria are numbered in the house format so the shared L2 judge
|
|
|
13
13
|
1: Silently ingests or processes material without confirmation, or claims the conversation never leaves the machine.
|
|
14
14
|
|
|
15
15
|
2. **World-Mapping Goal State —** a recorded run shape with staged raw material, a separate compile handoff, reviewed canonical paths, and event evidence for each accepted object.
|
|
16
|
-
5: Produces a recorded run shape with staged raw material, a separate compile handoff, reviewed/accepted canonical paths, and event evidence for each accepted object (`compile.accepted` for insight/org or `action_type.committed` for typed captures).
|
|
16
|
+
5: Produces a recorded run shape with staged raw material, a separate compile handoff, reviewed/accepted canonical paths, and event evidence for each accepted object (`compile.accepted` for insight/org or `action_type.committed` for typed captures); and the operator was shown each draft's content (path plus body per the surface-tiered rule in `specs/skills.md` §"Approval requires visible content"), not only its title or classification, before approving it.
|
|
17
17
|
3: Produces accepted material, but the evidence trail is incomplete or mixes draft/raw/canonical states imprecisely.
|
|
18
18
|
1: Treats raw source or drafts as canonical, bypasses review, or invents accepted objects.
|
|
19
19
|
|
|
@@ -50,8 +50,10 @@ The core-0049 build validated loader, registry, install, docs, and runtime-free
|
|
|
50
50
|
This build (change 0035 / core-0057) ships the minimal semantic fixture the L2 judge
|
|
51
51
|
scores against. That fixture must exercise a live interview or transcript/folder
|
|
52
52
|
curation, a recall give-back or honest negative, seed adoption with a keyword-near
|
|
53
|
-
distractor,
|
|
54
|
-
|
|
53
|
+
distractor, visible salience-ranking with an operator correction, per-beat expectation
|
|
54
|
+
evidence recorded before action, and — added by change 0045 / core-0059 — the draft/seed
|
|
55
|
+
content rendered before the approval checkpoint (the operator was shown a way to read what
|
|
56
|
+
they approved, per `specs/skills.md` §"Approval requires visible content"). The
|
|
55
57
|
fixture and its sanitized index ship in `skills/onboard/evals/` (see `GOLDENS.md`);
|
|
56
58
|
real operator-signed goldens live in the install per `specs/eval-discipline.md`, never
|
|
57
59
|
this public repo. The deterministic L1 gate (this rubric parses to seven criteria;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prd-draft
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.2.0
|
|
4
4
|
invocation:
|
|
5
5
|
explicit:
|
|
6
6
|
- "help me write a PRD"
|
|
@@ -18,6 +18,7 @@ reads:
|
|
|
18
18
|
- domains/{active_domain}/compiled/decisions/*
|
|
19
19
|
- domains/{active_domain}/compiled/commitments/*
|
|
20
20
|
- domains/{active_domain}/compiled/insights/*
|
|
21
|
+
- domains/{active_domain}/artifacts/prd/*
|
|
21
22
|
- codex/personal/*
|
|
22
23
|
- domains/{active_domain}/memory/long-term.md
|
|
23
24
|
writes:
|
|
@@ -25,7 +26,7 @@ writes:
|
|
|
25
26
|
- via_action_types: [enrich_person, log_decision, track_commitment, update_project_state]
|
|
26
27
|
sub_agents: []
|
|
27
28
|
requires_model_tier: standard
|
|
28
|
-
prompt_pin: gpt-5@2026-
|
|
29
|
+
prompt_pin: gpt-5@2026-07-12
|
|
29
30
|
---
|
|
30
31
|
|
|
31
32
|
# Skill: PRD Draft
|
|
@@ -44,9 +45,9 @@ The PM door may say “stakeholder”; the canonical object is a `person` in `co
|
|
|
44
45
|
|
|
45
46
|
### 1. Pull context before asking
|
|
46
47
|
|
|
47
|
-
Read the relevant project, decisions, people, commitments, insights, earned principles, and long-term memory. Do not ask for information that the workspace already answers. State the evidence you are relying on and distinguish fact from inference.
|
|
48
|
+
Read the relevant project, decisions, people, commitments, insights, earned principles, and long-term memory. When the invocation already supplies a trusted context packet or post-alignment scenario stage and says it represents declared reads, treat those facts as already read; do not spend turns re-reading the same paths. Otherwise issue all independent workspace reads together in one assistant turn. Do not ask for information that the workspace already answers. State the evidence you are relying on and distinguish fact from inference.
|
|
48
49
|
|
|
49
|
-
**Checkpoint 1 — context
|
|
50
|
+
**Checkpoint 1 — context and delta sweep.** After the context pull, separate trusted context that was already canonical before this session from state newly established, confirmed, or changed during this authoring session. Existing context can ground the PRD but is not a new capture. Identify possible deltas across a person insight, decision, owned hand-off, and project-state change. Do not emit anything yet if it is uncertain, merely implied, or already banked.
|
|
50
51
|
|
|
51
52
|
### 2. Ask only blocking questions
|
|
52
53
|
|
|
@@ -67,7 +68,7 @@ State a compact alignment brief: the problem, measurable outcome and guardrails,
|
|
|
67
68
|
|
|
68
69
|
### 4. Produce the PRD
|
|
69
70
|
|
|
70
|
-
Use this shape, adding detail only when it changes a decision.
|
|
71
|
+
Use this shape, adding detail only when it changes a decision. Target 750–900 words for a deterministic PRD and 950–1,100 words total for an AI PRD, including its appendix; 1,150 words is a hard whole-document ceiling. When no mechanical word counter is available, aim at the low end rather than claiming an unverified count. Use these budgets: problem + outcomes + non-goals ≤160 words; directional solution + capability table ≤190; rollout + readiness + tradeoffs + risks/open questions ≤270; AI appendix ≤300. Do not repeat a target, constraint, rationale, or rollout gate after a table or earlier section already states it. Before persisting, compress anything plausibly over the ceiling by removing repetition and implementation detail—not trusted targets, real unknowns, or exit gates. Express requirements as one compact capability table with no more than four rows. Limit alternatives to the two or three choices that materially change the product, each in one compact bullet carrying direction, rationale, tradeoff, and `Revisit if:`. One representative acceptance signal can be qualitative and binary; never invent a latency, duration, count, percentage, expiry, or other numeric target merely to make it look testable. Move exhaustive Given/expect cases, API fields, transports, components, UI controls, and other implementation detail to a follow-up spec unless the detail is already a trusted target or changes a product decision. Omit non-decision-making depth rather than appending it to this PRD.
|
|
71
72
|
|
|
72
73
|
```markdown
|
|
73
74
|
# <Feature or project> — PRD
|
|
@@ -82,15 +83,22 @@ Name the compiled decisions, insights, or people this rests on and add resolved
|
|
|
82
83
|
|
|
83
84
|
## Outcome and guardrails
|
|
84
85
|
Few measurable success criteria with targets, plus the counter-metrics that must not regress.
|
|
86
|
+
Preserve explicit targets already established in trusted state. Do not replace a known target with `[NEEDS INPUT]`, and do not invent a missing target merely to fill the shape.
|
|
85
87
|
|
|
86
88
|
## Non-goals
|
|
87
89
|
Explicit, co-equal exclusions that prevent scope drift — as clarifying as the goals.
|
|
88
90
|
|
|
89
91
|
## Directional solution
|
|
90
92
|
Enough product and technical shape to align — pass the squint test, not a premature blueprint.
|
|
93
|
+
Name only capabilities and observable behaviors grounded in trusted state. A category label such as “inbox,” “dashboard,” or “assistant” is not evidence for customary controls: do not add search, badges, queues, assignment, mark-as-done, refresh behavior, notifications, or similar UI conventions unless the trusted input names them. Keep an unresolved behavior in open questions rather than quietly turning it into a requirement.
|
|
91
94
|
|
|
92
95
|
## Requirements
|
|
93
|
-
|
|
96
|
+
| Capability | Observable behavior | One acceptance signal |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| <capability> | <what the user/system can observe> | <one representative Given/expect or trusted target> |
|
|
99
|
+
|
|
100
|
+
This is an alignment contract, not the complete acceptance-test suite. Do not expand the table into sub-requirements in the main body.
|
|
101
|
+
Every row must map to an explicit trusted target, decision, constraint, or user behavior. If you cannot point to that anchor, omit the row; do not make the PRD feel complete by importing a plausible category-default feature.
|
|
94
102
|
|
|
95
103
|
## Rollout and exit criteria
|
|
96
104
|
Phases, dependencies, and the evidence required to advance or stop — not dates alone.
|
|
@@ -99,7 +107,7 @@ Phases, dependencies, and the evidence required to advance or stop — not dates
|
|
|
99
107
|
Name the domain-shaped cross-functional owners and the checks they must clear before launch; do not add a generic checklist that changes no decision.
|
|
100
108
|
|
|
101
109
|
## Alternatives and tradeoffs
|
|
102
|
-
Alternatives considered, decisions made, and
|
|
110
|
+
Alternatives considered, decisions made, and a concrete `Revisit if:` condition for each chosen direction.
|
|
103
111
|
|
|
104
112
|
## Risks and open questions
|
|
105
113
|
Risks with mitigations. Use [NEEDS INPUT: …] for genuine unknowns.
|
|
@@ -107,42 +115,42 @@ Risks with mitigations. Use [NEEDS INPUT: …] for genuine unknowns.
|
|
|
107
115
|
|
|
108
116
|
The `Stakeholders` line and any per-role detail are contextual — include them when the work has real alignment owners (which it usually does, since you read `compiled/people`).
|
|
109
117
|
|
|
110
|
-
For a probabilistic (AI/LLM) feature, the PRD is a **behavioral contract**, not a behavior description.
|
|
118
|
+
For a probabilistic (AI/LLM) feature, the PRD is a **behavioral contract**, not a behavior description. Put the detailed contract in one in-document appendix so the decision-ready main body stays short while the document remains self-contained. Carry every component below, but express each once: at most four materially different behavior-matrix rows, one compact bullet block per remaining component, no repeated thresholds or rollout prose. Vague prose here ("should be accurate and concise") is the ambiguity the model resolves in production where you cannot see it:
|
|
111
119
|
|
|
112
120
|
```markdown
|
|
113
|
-
## Behavioral contract (AI features)
|
|
121
|
+
## Appendix A — Behavioral contract (AI features)
|
|
114
122
|
|
|
115
123
|
**Behavior matrix** — one row per input class:
|
|
116
124
|
|
|
117
125
|
| Input class | Target behavior | Acceptable degradation | Forbidden output |
|
|
118
126
|
|---|---|---|---|
|
|
119
127
|
|
|
120
|
-
**Acceptance as assertion.** Binary pass/fail per named failure mode, a % bar, and a named arbiter (e.g. "
|
|
128
|
+
**Acceptance as assertion.** Binary pass/fail per named failure mode, a % bar, and a named human or accountable role as arbiter (e.g. "the PRD owner reviews 20 outputs/sprint; >2 failures → back to prompt iteration"). Repeat the trusted eval-set ship and rollback percentages exactly; never invent a second threshold for this section. Not a Likert vibe.
|
|
121
129
|
|
|
122
|
-
**Eval set + thresholds.** A named, versioned dataset, a scoring metric, and an explicit ship threshold *and* rollback threshold
|
|
130
|
+
**Eval set + thresholds.** A named, versioned dataset, a scoring metric, and an explicit ship threshold *and* rollback threshold, both defined before building. The rollback threshold is a pre-build design choice, not a deferral: once the metric and ship threshold are set, state a concrete rollback trigger (a production safety-violation event and a live-quality floor) rather than leaving it `[NEEDS INPUT]`. If trusted state supplies a ship floor but no separate live-quality rollback floor, reuse that same trusted floor as the conservative production rollback floor and say explicitly that it is the same threshold; do not invent a second percentage and do not defer it.
|
|
123
131
|
|
|
124
132
|
**Two-layer success metrics.** Pair model-quality measures with product outcomes and an honest user-override counter-metric; do not let a good model score hide a feature users abandon or rewrite.
|
|
125
133
|
|
|
126
134
|
**Never-do constraints.** Specific, adjudicable rules ("must not cite a source absent from the retrieved context"), not "should not hallucinate."
|
|
127
135
|
|
|
128
|
-
**Failure-mode catalog.** Drawn from real prototype outputs, not imagined. If
|
|
136
|
+
**Failure-mode catalog.** Drawn from real prototype outputs, not imagined; keep observed failures visibly labelled as observed and any hypothesized mode separate. Do not add plausible hypothetical failures merely to fill out the catalog: include them only when trusted state also names the trace-review owner and due date that will test them. If even one observed failure is supplied, list every supplied observed failure and do not replace the evidence with an empty `[NEEDS INPUT]` placeholder. Add a trace-review owner-and-due plan only for a genuinely missing broader review, and name a specific owner and due date rather than a bare placeholder. If no prototype outputs exist yet, mark `[NEEDS INPUT: trace-review owner and due date]` as a pre-launch blocker rather than inventing a catalog.
|
|
129
137
|
|
|
130
|
-
**Fallback + model strategy.**
|
|
138
|
+
**Fallback + model strategy.** Use this exact three-part mini-contract: `Primary capability profile:` the tool-use/reasoning capabilities required and their role; `Fallback capability profile:` the reduced or alternate model capability and safe degradation; `Switch when:` an observable quality, latency, refusal, or availability condition. Also say what the user sees on uncertainty/refusal/timeout. Do not pin a model version. A client or connector such as Claude Desktop or ChatGPT is not itself a model fallback.
|
|
131
139
|
|
|
132
|
-
**Ops readiness.** Golden-dataset owner + due date; cost-at-scale
|
|
140
|
+
**Ops readiness.** Golden-dataset owner + due date; cost-at-scale as an explicit `tokens × volume × price` calculation. If inference is paid by an external client, write the equation explicitly as `0 product-paid tokens × call volume × model price = $0 product inference cost`, name who pays instead, and separately name the server-side cost drivers and missing inputs.
|
|
133
141
|
```
|
|
134
142
|
|
|
135
|
-
**Checkpoint 4 — artifact plus capture
|
|
143
|
+
**Checkpoint 4 — artifact plus capture plan.** Do not invoke a canonical action before the PRD artifact is written. Once it exists, make a four-row capture plan—decision, commitment, project, person—with the exact trusted source, whether it is new this session, and `capture` or `skip` plus the reason. For every decision row, preflight four fields from trusted transition state: direction, rationale, cost/tradeoff, and evidence. Resolve those fields in this order: explicit operator rationale; named evidence; constraints and risks directly tied to the choice; only then a labeled inference. Preserve every available source field in the action. Do not replace a central supplied user risk, inherited-platform constraint, external dependency, or irreversible-harm concern with a different plausible tradeoff. Execute every `capture` row and no `skip` row in one tool-call wave. Do not stop after decisions. An honest zero is better than invented state; the coverage target counts warranted instances, not action-type variety.
|
|
136
144
|
|
|
137
145
|
### 5. Self-review against the quality bar
|
|
138
146
|
|
|
139
147
|
Before persisting, grade the draft against `evals/rubric.md` and apply three forcing tests. Tighten any criterion below the bar; where a gap is real, mark it honestly with `[NEEDS INPUT: …]` rather than padding. A smaller honest PRD beats a bloated one.
|
|
140
148
|
|
|
141
|
-
- **Squint test** — can a reader squint and see the same product shape? If not, the directional solution is over- or under-specified.
|
|
149
|
+
- **Squint test** — can a reader squint and see the same product shape? If not, the directional solution is over- or under-specified. Remove field names, transport mechanics, components, UI controls, and exhaustive acceptance cases from the main body unless they change the product choice. Audit every capability and requirements row back to a trusted fact; delete category-default UI invented from the feature name, and return unresolved behaviors to open questions.
|
|
142
150
|
- **The $5,000 bet** — would you bet your own money on this? Forces value, outcome, and testability to the surface.
|
|
143
151
|
- **Disappointment test** — would real users be disappointed to lose this? If the answer is soft, the problem is under-developed.
|
|
144
152
|
|
|
145
|
-
**Checkpoint 5 — quality self-check.** Confirm the problem reads standalone and is evidenced; success criteria are few, targeted, and carry a counter-metric; non-goals are co-equal; each rollout phase has exit criteria, not just a date; launch readiness is named when relevant; unknowns and tradeoffs are honest, not a sales pitch. For an AI feature, confirm every component
|
|
153
|
+
**Checkpoint 5 — quality and compression self-check.** Confirm the problem reads standalone and is evidenced; success criteria are few, targeted, and carry a counter-metric; non-goals are co-equal; every chosen alternative has a `Revisit if:` condition; each rollout phase has exit criteria, not just a date; launch readiness is named when relevant; unknowns and tradeoffs are honest, not a sales pitch. Compress toward 750–900 words for a deterministic PRD or 950–1,100 total words for an AI PRD and treat 1,150 whole-document words as a hard failure. Remove repeated targets/rationales, invented numeric acceptance details, and detailed mechanics first; never compress away trusted targets, genuine `[NEEDS INPUT]` items, or phase exit evidence. Keep requirements in the compact capability table and omit implementation depth from this PRD. For an AI feature, put the terse behavioral contract under `Appendix A`, use no more than four behavior rows, and confirm every component is present without repeating main-body content. Acceptance repeats the trusted percentages exactly, and the eval-set defines both a ship and a rollback threshold rather than deferring rollback. When no distinct trusted rollback percentage exists, the live-quality rollback floor must explicitly reuse the trusted ship floor. Confirm the failure catalog retains every supplied observed failure, the fallback block literally names `Primary capability profile`, `Fallback capability profile`, and `Switch when`, connectors are not mistaken for model fallbacks, and ops readiness includes a literal cost equation. This author-side review is not evidence that the L2 gate passed; it identifies gaps for the operator and T7.6 corpus to judge.
|
|
146
154
|
|
|
147
155
|
### 6. Persist or refine the durable artifact
|
|
148
156
|
|
|
@@ -165,18 +173,18 @@ Optional `links:` use the standard link grammar. A new `{slug}` creates a new ar
|
|
|
165
173
|
|
|
166
174
|
### 7. Bank warranted canonical context
|
|
167
175
|
|
|
168
|
-
Use the runtime action path for trusted captures. Do not write `compiled/` or `codex/` directly.
|
|
176
|
+
Use the runtime action path for trusted captures. Do not write `compiled/` or `codex/` directly. Execute from the post-artifact four-row plan. Read the action results before finishing. If an action is rejected because its payload is malformed, correct that same warranted action immediately; do not substitute a different capture to make the count look complete. Omit `exposure` unless the operator explicitly changes it—the action default is private—and never turn a contextual evidence label into an exposure value. Batch independent capture actions in one assistant turn so a correct dense run leaves room for one correction wave and a final response.
|
|
169
177
|
|
|
170
178
|
| When independently warranted | Action type | Required care |
|
|
171
179
|
|---|---|---|
|
|
172
|
-
| A durable scope or priority choice is made | `log_decision` |
|
|
173
|
-
| A
|
|
174
|
-
|
|
|
175
|
-
|
|
|
180
|
+
| A durable scope or priority choice is newly made, newly confirmed as unbanked, or materially changed in this session | `log_decision` | Capture the smallest sufficient set. Do not re-log every listed decision or every constraint read from canonical state. Make the title standalone: name the product or capability subject and the chosen direction; a generic title such as `Deployment model` or `Architecture choice` is invalid even when the body supplies the missing subject. When one established choice joins multiple load-bearing directions, carry every axis in the title rather than naming only one component. Preserve the trusted direction, rationale, cost/tradeoff, and evidence without substituting a different justification; include expected outcome, success signal, and a review prompt when high stakes. |
|
|
181
|
+
| A human’s durable priority, constraint, or knowledge changes | `enrich_person` | The target must be a person, not a team or organization. Supply the canonical person slug as `name` and the new `insight`; only a genuinely new person gets create-only `title`, `exposure`, or `links` and may need approval. For an existing person, omit all create-only fields. |
|
|
182
|
+
| A newly established owned hand-off has a real due date | `track_commitment` | Supply `title`, exact `due`, and a `body` beginning `Owner: <full name>.` followed by the deliverable and acceptance evidence. The V1 action has no typed owner field; do not invent one. Do not turn a date proposal or owner inference into a commitment. |
|
|
183
|
+
| This PRD run materially changes a named project’s scope, status, lifecycle, or next gate | `update_project_state` | Supply the canonical project slug as `name` and one compact `update` covering the PRD lifecycle, core direction, success guardrail, launch perimeter when defined (such as tiers or connectors), phase gate, material open questions, and inherited external dependencies. Only a genuinely new project gets create-only `title`, `exposure`, or `links` and may need approval. For an existing project, omit all create-only fields. |
|
|
176
184
|
|
|
177
|
-
Use links from a newly-created decision or commitment to
|
|
185
|
+
Use semantically valid links from a newly-created decision or commitment to an existing project/person hub only when the current link vocabulary and target grammar can express the relationship. A PRD artifact path is not a valid canonical action link target: never add `artifacts/prd/...` to an action payload. Omit a link when no valid compiled target and relation exist. Do not misuse `cites`, `decided-by`, or another relation merely to encode commitment ownership. Existing people and projects are append-only log targets; never attempt a cross-link by mutating their frontmatter.
|
|
178
186
|
|
|
179
|
-
**Checkpoint 6 — refinement sweep.** After each material refinement, reassess only newly established trusted facts. Re-run the artifact refinement procedure if the PRD changed. Do not manufacture captures to increase a count.
|
|
187
|
+
**Checkpoint 6 — refinement sweep.** After each material refinement, reassess only newly established trusted facts. Re-run the artifact refinement procedure if the PRD changed. Do not manufacture captures to increase a count. If multiple actions are rejected for the same payload defect, correct and resend every rejected warranted action together in the next tool-call wave; do not repair them one per turn.
|
|
180
188
|
|
|
181
189
|
### 8. Return a compact handoff
|
|
182
190
|
|
|
@@ -186,8 +194,8 @@ Return the artifact path, the decisions and commitments actually banked, the ope
|
|
|
186
194
|
|
|
187
195
|
Reads and the durable artifact write continue unchanged. Write/refine the PRD directly at `domains/{active_domain}/artifacts/prd/{slug}.md`, keep it private, and obey the same explicit-refinement rule. It emits no event.
|
|
188
196
|
|
|
189
|
-
Each intended canonical action becomes a separate untrusted capture in `domains/{domain}/raw/degraded/YYYY-MM-DD-prd-draft-{slug}.md`, never in `compiled/` or `codex/`. Make `{slug}` unique per capture: compose `<prd-slug>-<action-type>-<capture-slug>` (for example, `search-quality-log-decision-scope-first`) so dense captures for one PRD never collide. Its frontmatter contains `degraded: true`, `intended_action_type`, `skill: prd-draft`, `version: 0.
|
|
197
|
+
Each intended canonical action becomes a separate untrusted capture in `domains/{domain}/raw/degraded/YYYY-MM-DD-prd-draft-{slug}.md`, never in `compiled/` or `codex/`. Make `{slug}` unique per capture: compose `<prd-slug>-<action-type>-<capture-slug>` (for example, `search-quality-log-decision-scope-first`) so dense captures for one PRD never collide. Its frontmatter contains `degraded: true`, `intended_action_type`, `skill: prd-draft`, `version: 0.2.0`, `created`, `trust_tier: untrusted`, and the payload the action would have received. Do not emit or back-fill events. When the runtime is available, reconcile captures only through `compile` and the normal action gates.
|
|
190
198
|
|
|
191
199
|
## Evaluation status
|
|
192
200
|
|
|
193
|
-
`skills/prd-draft/evals/rubric.md` defines the L2 judgment criteria.
|
|
201
|
+
`skills/prd-draft/evals/rubric.md` defines the L2 judgment criteria. The operator-signed contrast pairs, model-run coverage, and reactive-untrusted negative passed T7.6 at the reliability floor for this exact `prompt_pin`; `evals/PINS.md` records the accepted baseline.
|
|
@@ -24,7 +24,7 @@ Mesh coverage and untrusted-input restraint are **separate axes**, scored elsewh
|
|
|
24
24
|
11. **Behavioral constraints** — specific never-do rules a new hire could adjudicate. Failure: "should not hallucinate."
|
|
25
25
|
12. **Failure-mode catalog** — drawn from real prototype outputs; before those exist, an explicit trace-review owner and due date are a pre-launch blocker. Failure: imagined as evidence; absent without an evidence-collection plan.
|
|
26
26
|
13. **Fallback + model strategy** — refusal/uncertainty/timeout behavior plus a capability profile, a primary/fallback, and switch logic. Failure: happy-path only; a pinned model version.
|
|
27
|
-
14. **Ops readiness** — a golden-dataset owner + due date; cost-at-scale (tokens × volume × price). Failure: both skipped.
|
|
27
|
+
14. **Ops readiness** — a golden-dataset owner + due date; cost-at-scale (tokens × volume × price). When an external client pays inference, an explicit `$0` product-inference equation plus the product's server-side cost drivers and honestly marked missing sizing inputs satisfies the cost requirement; do not require invented volume. Failure: both skipped.
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
// one garbled entry disables the whole action layer with an error naming it. A config
|
|
41
41
|
// error is loud and total, never partial.
|
|
42
42
|
|
|
43
|
-
import { SLUG_SOURCE } from './contract.js';
|
|
43
|
+
import { SLUG_SOURCE, RE_SLUG } from './contract.js';
|
|
44
44
|
|
|
45
45
|
export const PERMISSION_ORDER = ['auto-run', 'ask-once-if-new', 'requires-approval'];
|
|
46
46
|
|
|
@@ -368,8 +368,11 @@ export function resolveActionTypesAgainst({ registry, codeTable }) {
|
|
|
368
368
|
// resolution depends on it, so a malformed list fails closed HERE — the one
|
|
369
369
|
// validator both `act` and `registry status` consume (a status that calls the
|
|
370
370
|
// layer disabled while act still writes is the two-matchers drift, rules.md).
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
// Enforce the SLUG the message already claims (Codex R3 F3): a non-slug domain (e.g. "../../outside")
|
|
372
|
+
// would path-traverse when any surface joins it into domains/{domain}; the registry is the trust
|
|
373
|
+
// root every domain resolution reads, so fail closed here, not only in each caller's containment guard.
|
|
374
|
+
if (!Array.isArray(registry.domains) || registry.domains.some((d) => typeof d !== 'string' || !RE_SLUG.test(d))) {
|
|
375
|
+
errors.push('domains: must be a list of domain-slug strings (kebab-case; every action resolves its domain against it)');
|
|
373
376
|
}
|
|
374
377
|
|
|
375
378
|
const section = registry.action_types;
|
|
@@ -56,7 +56,6 @@ import { checkObjectContent } from './validate-object.js';
|
|
|
56
56
|
import { appendEvent, readEvents, localDateStamp } from './event-log.js';
|
|
57
57
|
import { digestFile } from './content-digest.js';
|
|
58
58
|
import { resolveInstall } from './install-root.js';
|
|
59
|
-
import { DEFAULT_DOMAIN } from './layout.js';
|
|
60
59
|
import {
|
|
61
60
|
slugify,
|
|
62
61
|
serializeFrontmatter,
|
|
@@ -67,6 +66,26 @@ import {
|
|
|
67
66
|
stageValidateRenameAll,
|
|
68
67
|
} from './action-writers.js';
|
|
69
68
|
|
|
69
|
+
// A POST-WRITE failure (Codex R4 F2): the artifact IS on disk, but a later audit-append / hook step
|
|
70
|
+
// failed. The error carries `postWrite: true` so a surface adapter treats the mutation as LANDED (and
|
|
71
|
+
// records it in its honest-cancellation ledger), not as a refusal — event-log.md:92 "artifact written
|
|
72
|
+
// but audit failed" spine case. A pre-write refusal is a plain Error (no flag).
|
|
73
|
+
function postWriteError(message) {
|
|
74
|
+
const e = new Error(message);
|
|
75
|
+
e.postWrite = true;
|
|
76
|
+
return e;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A PRE-write cancellation (Codex R6 F1): an OPTIONAL `ctx.signal` aborted BEFORE the durable write —
|
|
80
|
+
// so no artifact landed. `aborted: true` lets a surface report "cancelled", NOT "refused", and NOT as
|
|
81
|
+
// a post-write landed mutation. Backward-compatible: with no `ctx.signal` there is no check and no
|
|
82
|
+
// behavior change for any existing caller.
|
|
83
|
+
function abortedError(type) {
|
|
84
|
+
const e = new Error(`${type}: the turn was cancelled before the durable write — nothing was written.`);
|
|
85
|
+
e.aborted = true;
|
|
86
|
+
return e;
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
// --- Payload schemas (D2 — hand-rolled, zero-dep) ------------------------------------
|
|
71
90
|
//
|
|
72
91
|
// A JS field-spec object per action satisfies ontology's "JSON schema" for 6 fixed
|
|
@@ -1193,6 +1212,61 @@ export function resolveActionTypes(registry) {
|
|
|
1193
1212
|
return resolveActionTypesAgainst({ registry, codeTable: ACTION_REGISTRY });
|
|
1194
1213
|
}
|
|
1195
1214
|
|
|
1215
|
+
// `describeActionSchema` — read-only resolution of ONE action type's EFFECTIVE payload
|
|
1216
|
+
// schema for `phronesis act <type> --schema` (change 0045). It reads the SAME
|
|
1217
|
+
// `.phronesis/registry.json` action-type table the action core validates against (so it
|
|
1218
|
+
// reflects any operator schema tightening, not just the code defaults) and returns plain,
|
|
1219
|
+
// deep-copied data the CLI formats. No write, no event, no approval — a caller reads the
|
|
1220
|
+
// payload contract instead of learning it by submitting empty payloads. All install/
|
|
1221
|
+
// registry access lives here so the `act` adapter stays a pure parse-and-delegate surface
|
|
1222
|
+
// (validate-actions D7b). Returns a discriminated result:
|
|
1223
|
+
// { status: 'error', message } install not found / registry unreadable
|
|
1224
|
+
// { status: 'invalid-registry', errors } the action layer is disabled (all-or-nothing)
|
|
1225
|
+
// { status: 'unknown-type', type, knownTypes, isCodeType }
|
|
1226
|
+
// { status: 'ok', type, source, fields } fields: [{ name, kind, required, nonEmpty, values? }]
|
|
1227
|
+
export async function describeActionSchema({ dir, cwd, type } = {}) {
|
|
1228
|
+
const installed = resolveInstall({ dir, cwd });
|
|
1229
|
+
if (installed.error) return { status: 'error', message: installed.error };
|
|
1230
|
+
const installRoot = installed.root;
|
|
1231
|
+
|
|
1232
|
+
let registry;
|
|
1233
|
+
try {
|
|
1234
|
+
registry = await fse.readJson(join(installRoot, '.phronesis', 'registry.json'));
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
return {
|
|
1237
|
+
status: 'error',
|
|
1238
|
+
message: `could not read the install registry (.phronesis/registry.json): ${err.message}`,
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
const { errors, source, effective } = resolveActionTypes(registry);
|
|
1243
|
+
if (errors.length) return { status: 'invalid-registry', errors };
|
|
1244
|
+
|
|
1245
|
+
// Own-property check, never a truthy prototype-chain lookup (the repo convention for
|
|
1246
|
+
// name-keyed JSON maps): `act constructor --schema` / `toString` / `__proto__` must be
|
|
1247
|
+
// unknown types, not Object.prototype members wearing a trench coat that reach the OK
|
|
1248
|
+
// path with an empty field list.
|
|
1249
|
+
if (!Object.hasOwn(effective, type)) {
|
|
1250
|
+
return {
|
|
1251
|
+
status: 'unknown-type',
|
|
1252
|
+
type,
|
|
1253
|
+
knownTypes: Object.keys(effective).sort(),
|
|
1254
|
+
isCodeType: Object.hasOwn(ACTION_REGISTRY, type),
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const entry = effective[type];
|
|
1258
|
+
|
|
1259
|
+
// Plain, deep-copied field-spec list — no live reference to the code table escapes.
|
|
1260
|
+
const fields = Object.entries(entry.schema || {}).map(([name, spec]) => ({
|
|
1261
|
+
name,
|
|
1262
|
+
kind: spec.kind,
|
|
1263
|
+
required: spec.required === true,
|
|
1264
|
+
nonEmpty: spec.nonEmpty === true,
|
|
1265
|
+
...(spec.values !== undefined ? { values: [...spec.values] } : {}),
|
|
1266
|
+
}));
|
|
1267
|
+
return { status: 'ok', type, source, fields };
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1196
1270
|
// --- Permission gate (D5 — detective; specs/trust-scopes.md Scope 1) ------------------
|
|
1197
1271
|
|
|
1198
1272
|
export function permissionGate(permission, { exists = false, approvalSource } = {}) {
|
|
@@ -1217,9 +1291,12 @@ export function permissionGate(permission, { exists = false, approvalSource } =
|
|
|
1217
1291
|
// --- Execution-context resolution (D10) -----------------------------------------------
|
|
1218
1292
|
|
|
1219
1293
|
// Resolve the active domain: explicit ctx.domain, else the domains/{slug}/ the cwd sits
|
|
1220
|
-
// in, else
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1294
|
+
// in, else the sole registered domain (core-0061 D4 — the old DEFAULT_DOMAIN "pm" fallback
|
|
1295
|
+
// was wrong for every single-domain install not named pm) — and the resolved slug MUST be
|
|
1296
|
+
// in the install registry. With no signal and several domains registered, refuse and list
|
|
1297
|
+
// them; with none registered, refuse. An unregistered domain refuses; an action must never
|
|
1298
|
+
// conjure a phantom domains/{slug}/ tree as a side effect (the event-log --dir fail-closed
|
|
1299
|
+
// lesson, applied to domains).
|
|
1223
1300
|
// The registry is read ONCE per runAction (it also carries action_types since T2.4)
|
|
1224
1301
|
// and passed in parsed. EXPORTED since core-0014: the retrieval surface (search.js)
|
|
1225
1302
|
// resolves its default scope through this exact function — one resolver, two consumers
|
|
@@ -1231,9 +1308,21 @@ export function resolveDomain({ installRoot, registry, domain, cwd }) {
|
|
|
1231
1308
|
const parts = relative(installRoot, resolve(cwd)).split(sep);
|
|
1232
1309
|
if (parts[0] === 'domains' && parts[1]) slug = parts[1];
|
|
1233
1310
|
}
|
|
1234
|
-
if (!slug) slug = DEFAULT_DOMAIN;
|
|
1235
1311
|
|
|
1236
1312
|
const registered = Array.isArray(registry.domains) ? registry.domains : [];
|
|
1313
|
+
// No explicit --domain and cwd not inside a domains/<slug>/ tree: the sole registered
|
|
1314
|
+
// domain wins (core-0061 D4). The install's one domain is the unambiguous default; the old
|
|
1315
|
+
// "pm" literal defaulted a business install onto a phantom domain and refused. Several
|
|
1316
|
+
// registered → cannot guess; refuse and list them so the caller picks with --domain. One
|
|
1317
|
+
// resolver, every domain-scoped verb inherits (lint, search, query, ingest, act, …).
|
|
1318
|
+
if (!slug) {
|
|
1319
|
+
if (registered.length === 1) return registered[0];
|
|
1320
|
+
throw new Error(
|
|
1321
|
+
registered.length === 0
|
|
1322
|
+
? 'no domain specified and none are registered in this installation. Refusing — actions never create a domain as a side effect.'
|
|
1323
|
+
: `no domain specified and this installation has ${registered.length} registered (${registered.join(', ')}). Pass --domain <slug> to choose one.`,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1237
1326
|
if (!registered.includes(slug)) {
|
|
1238
1327
|
throw new Error(
|
|
1239
1328
|
`domain "${slug}" is not registered in this installation (registered: ${registered.join(', ') || 'none'}). Refusing — actions never create a domain as a side effect.`,
|
|
@@ -1248,7 +1337,7 @@ export function resolveDomain({ installRoot, registry, domain, cwd }) {
|
|
|
1248
1337
|
// dir explicit install dir (marker-checked; optional)
|
|
1249
1338
|
// cwd where the caller runs (install/domain discovery; default process.cwd())
|
|
1250
1339
|
// session REQUIRED — session_id on both events
|
|
1251
|
-
// domain explicit domain slug (else cwd-derived, else
|
|
1340
|
+
// domain explicit domain slug (else cwd-derived, else the sole registered domain; D10 / core-0061 D4)
|
|
1252
1341
|
// actor / surface event identity (defaults: agent / cli, like `event emit`)
|
|
1253
1342
|
// invocation_id attribution provenance supplied by a skill adapter or controlled
|
|
1254
1343
|
// harness; caller-controlled metadata, never authority
|
|
@@ -1277,6 +1366,10 @@ export async function runAction({ type, payload = {}, ctx = {} } = {}) {
|
|
|
1277
1366
|
? ctx.invocation_id
|
|
1278
1367
|
: undefined;
|
|
1279
1368
|
const now = ctx.now || new Date();
|
|
1369
|
+
// OPTIONAL cancel signal (Codex R6 F1): a surface may pass ctx.signal to close the TOCTOU window —
|
|
1370
|
+
// re-checked immediately before each durable write. Absent ⇒ no check (backward-compatible).
|
|
1371
|
+
// Named cancelSignal to avoid colliding with the approval-signal boolean below.
|
|
1372
|
+
const cancelSignal = ctx.signal;
|
|
1280
1373
|
|
|
1281
1374
|
// invocation_id is execution-context provenance, not action semantics. A model can
|
|
1282
1375
|
// place arbitrary JSON in payload, so discard a payload-supplied value before schema
|
|
@@ -1452,6 +1545,7 @@ export async function runAction({ type, payload = {}, ctx = {} } = {}) {
|
|
|
1452
1545
|
// The draft overwrites any prior candidate at this path (idempotent convergence: an
|
|
1453
1546
|
// identical re-draft re-fires the same candidate, no new canonical state).
|
|
1454
1547
|
if (built.draft) {
|
|
1548
|
+
if (cancelSignal?.aborted) throw abortedError(type); // R6 F1: no draft write lands after a cancel
|
|
1455
1549
|
await stageValidateRename({
|
|
1456
1550
|
installRoot,
|
|
1457
1551
|
relPath: built.draft.writeRelPath,
|
|
@@ -1488,13 +1582,14 @@ export async function runAction({ type, payload = {}, ctx = {} } = {}) {
|
|
|
1488
1582
|
// Same "WAS written, but the audit failed" guarantee as the committed path: the
|
|
1489
1583
|
// draft already landed; say so plainly so a re-run is an idempotent re-draft, not a
|
|
1490
1584
|
// blind retry into a confusing state.
|
|
1491
|
-
throw
|
|
1585
|
+
throw postWriteError(
|
|
1492
1586
|
`${type}: the draft WAS written to ${built.draft.writeRelPath}, but the compile.candidate audit event failed to append: ${err.message}`,
|
|
1493
1587
|
);
|
|
1494
1588
|
}
|
|
1495
1589
|
return { ok: true, path: built.draft.writeRelPath, draft: true, changes_summary: built.changesSummary };
|
|
1496
1590
|
}
|
|
1497
1591
|
|
|
1592
|
+
if (cancelSignal?.aborted) throw abortedError(type); // R6 F1: no canonical write lands after a cancel
|
|
1498
1593
|
if (built.writes) {
|
|
1499
1594
|
if (built.writes.length) await stageValidateRenameAll({ installRoot, writes: built.writes });
|
|
1500
1595
|
} else {
|
|
@@ -1539,10 +1634,13 @@ export async function runAction({ type, payload = {}, ctx = {} } = {}) {
|
|
|
1539
1634
|
now,
|
|
1540
1635
|
});
|
|
1541
1636
|
} catch (err) {
|
|
1637
|
+
if (wrote.length) {
|
|
1638
|
+
throw postWriteError(
|
|
1639
|
+
`${type}: the artifact WAS written to ${wrote.join(' and ')}, but the action_type.committed audit event failed to append: ${err.message}`,
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1542
1642
|
throw new Error(
|
|
1543
|
-
|
|
1544
|
-
? `${type}: the artifact WAS written to ${wrote.join(' and ')}, but the action_type.committed audit event failed to append: ${err.message}`
|
|
1545
|
-
: `${type}: the run was a no-op (nothing written), but the action_type.committed audit event failed to append: ${err.message}`,
|
|
1643
|
+
`${type}: the run was a no-op (nothing written), but the action_type.committed audit event failed to append: ${err.message}`,
|
|
1546
1644
|
);
|
|
1547
1645
|
}
|
|
1548
1646
|
|
|
@@ -1559,19 +1657,25 @@ export async function runAction({ type, payload = {}, ctx = {} } = {}) {
|
|
|
1559
1657
|
event: committedEvent,
|
|
1560
1658
|
});
|
|
1561
1659
|
} catch (err) {
|
|
1660
|
+
if (wrote.length) {
|
|
1661
|
+
throw postWriteError(
|
|
1662
|
+
`${type}: the artifact WAS written to ${wrote.join(' and ')}, but hook execution after action_type.committed failed: ${err.message}`,
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1562
1665
|
throw new Error(
|
|
1563
|
-
|
|
1564
|
-
? `${type}: the artifact WAS written to ${wrote.join(' and ')}, but hook execution after action_type.committed failed: ${err.message}`
|
|
1565
|
-
: `${type}: the run was a no-op (nothing written), but hook execution after action_type.committed failed: ${err.message}`,
|
|
1666
|
+
`${type}: the run was a no-op (nothing written), but hook execution after action_type.committed failed: ${err.message}`,
|
|
1566
1667
|
);
|
|
1567
1668
|
}
|
|
1568
1669
|
if (committedHooks.gateFailures.length) {
|
|
1569
1670
|
const f = committedHooks.gateFailures[0];
|
|
1570
1671
|
const detail = `${f.error}${f.stderrTail ? ` — ${f.stderrTail.trim()}` : ''}`;
|
|
1672
|
+
if (wrote.length) {
|
|
1673
|
+
throw postWriteError(
|
|
1674
|
+
`${type}: the artifact WAS written to ${wrote.join(' and ')}, but gate hook "${f.id}" failed at action_type.committed: ${detail} (audited in .phronesis/audit.log; the write is not rolled back)`,
|
|
1675
|
+
);
|
|
1676
|
+
}
|
|
1571
1677
|
throw new Error(
|
|
1572
|
-
|
|
1573
|
-
? `${type}: the artifact WAS written to ${wrote.join(' and ')}, but gate hook "${f.id}" failed at action_type.committed: ${detail} (audited in .phronesis/audit.log; the write is not rolled back)`
|
|
1574
|
-
: `${type}: the run was a no-op (nothing written), but gate hook "${f.id}" failed at action_type.committed: ${detail} (audited in .phronesis/audit.log)`,
|
|
1678
|
+
`${type}: the run was a no-op (nothing written), but gate hook "${f.id}" failed at action_type.committed: ${detail} (audited in .phronesis/audit.log)`,
|
|
1575
1679
|
);
|
|
1576
1680
|
}
|
|
1577
1681
|
|
package/vendor/core/src/index.js
CHANGED
|
@@ -55,9 +55,12 @@ export {
|
|
|
55
55
|
CONVERGENT_COMMITTED_HOOKS,
|
|
56
56
|
defaultActionTypesSection,
|
|
57
57
|
resolveActionTypes,
|
|
58
|
+
describeActionSchema,
|
|
58
59
|
resolveDomain,
|
|
59
60
|
} from './actions.js';
|
|
60
61
|
|
|
62
|
+
export { matchFrontmatterBlock } from './workspace-scan.js';
|
|
63
|
+
|
|
61
64
|
export {
|
|
62
65
|
buildActiveContextProjection,
|
|
63
66
|
refreshActiveContext,
|