@shardworks/astrolabe-apparatus 0.1.212 → 0.1.214
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 +1 -1
- package/dist/engines/index.d.ts +1 -1
- package/dist/engines/index.d.ts.map +1 -1
- package/dist/engines/index.js +1 -1
- package/dist/engines/index.js.map +1 -1
- package/dist/engines/spec-publish.d.ts +6 -16
- package/dist/engines/spec-publish.d.ts.map +1 -1
- package/dist/engines/spec-publish.js +7 -76
- package/dist/engines/spec-publish.js.map +1 -1
- package/package.json +8 -8
- package/sage-analyst.md +17 -14
- package/sage-reading-analyst.md +17 -14
package/README.md
CHANGED
|
@@ -159,7 +159,7 @@ The Astrolabe declares one book in Stacks:
|
|
|
159
159
|
| `astrolabe.plan-init` | Creates a PlanDoc from the brief writ; validates codex presence |
|
|
160
160
|
| `astrolabe.inventory-check` | Validates that the reader produced a non-empty inventory |
|
|
161
161
|
| `astrolabe.decision-review` | Two-pass engine: blocks for patron review, then reconciles answers. Decisions with `selected` already pre-set by the analyst are auto-accepted — they are excluded from the InputRequestDoc, and if nothing remains reviewable the engine fast-paths to `writing` without opening the gate. |
|
|
162
|
-
| `astrolabe.spec-publish` | Publishes the generated specification as a new writ |
|
|
162
|
+
| `astrolabe.spec-publish` | Publishes the generated specification as a new writ. Posts the spec body verbatim — any `<task-manifest>` block is preserved and is not fanned out into child `piece` writs. |
|
|
163
163
|
|
|
164
164
|
### Rig Templates (contributed to Spider)
|
|
165
165
|
|
package/dist/engines/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createPlanInitEngine } from './plan-init.ts';
|
|
2
2
|
export { createInventoryCheckEngine } from './inventory-check.ts';
|
|
3
3
|
export { createDecisionReviewEngine } from './decision-review.ts';
|
|
4
|
-
export { createSpecPublishEngine
|
|
4
|
+
export { createSpecPublishEngine } from './spec-publish.ts';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/engines/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createPlanInitEngine } from "./plan-init.js";
|
|
2
2
|
export { createInventoryCheckEngine } from "./inventory-check.js";
|
|
3
3
|
export { createDecisionReviewEngine } from "./decision-review.js";
|
|
4
|
-
export { createSpecPublishEngine
|
|
4
|
+
export { createSpecPublishEngine } from "./spec-publish.js";
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/engines/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* the originating brief via a 'refines' link, records generatedWritId on
|
|
6
6
|
* the PlanDoc, and transitions the plan to 'completed'.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The engine always posts the full spec verbatim — including any
|
|
9
|
+
* `<task-manifest>` block the spec-writer produced. The planning pipeline
|
|
10
|
+
* does not fan the manifest out into child `piece` writs; the implementing
|
|
11
|
+
* artificer (or a downstream tool) is responsible for acting on the
|
|
12
|
+
* manifest if it chooses to. This keeps spec-publish a pure "publish what
|
|
13
|
+
* was written" step with no hidden surgery on the mandate body.
|
|
13
14
|
*
|
|
14
15
|
* Preconditions:
|
|
15
16
|
* - plan.status must be 'writing'
|
|
@@ -18,16 +19,5 @@
|
|
|
18
19
|
import type { EngineDesign } from '@shardworks/fabricator-apparatus';
|
|
19
20
|
import type { Book } from '@shardworks/stacks-apparatus';
|
|
20
21
|
import type { PlanDoc } from '../types.ts';
|
|
21
|
-
/**
|
|
22
|
-
* Parse the `<task-manifest>` block from the spec string.
|
|
23
|
-
* Returns the individual `<task ...>...</task>` fragments and the spec
|
|
24
|
-
* with the manifest block removed.
|
|
25
|
-
*
|
|
26
|
-
* If no manifest is found, returns null.
|
|
27
|
-
*/
|
|
28
|
-
export declare function parseTaskManifest(spec: string): {
|
|
29
|
-
tasks: string[];
|
|
30
|
-
strippedSpec: string;
|
|
31
|
-
} | null;
|
|
32
22
|
export declare function createSpecPublishEngine(getPlansBook: () => Book<PlanDoc>): EngineDesign;
|
|
33
23
|
//# sourceMappingURL=spec-publish.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-publish.d.ts","sourceRoot":"","sources":["../../src/engines/spec-publish.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spec-publish.d.ts","sourceRoot":"","sources":["../../src/engines/spec-publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAqC,MAAM,kCAAkC,CAAC;AACxG,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAEzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,YAAY,CA+DvF"}
|
|
@@ -5,44 +5,18 @@
|
|
|
5
5
|
* the originating brief via a 'refines' link, records generatedWritId on
|
|
6
6
|
* the PlanDoc, and transitions the plan to 'completed'.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* The engine always posts the full spec verbatim — including any
|
|
9
|
+
* `<task-manifest>` block the spec-writer produced. The planning pipeline
|
|
10
|
+
* does not fan the manifest out into child `piece` writs; the implementing
|
|
11
|
+
* artificer (or a downstream tool) is responsible for acting on the
|
|
12
|
+
* manifest if it chooses to. This keeps spec-publish a pure "publish what
|
|
13
|
+
* was written" step with no hidden surgery on the mandate body.
|
|
13
14
|
*
|
|
14
15
|
* Preconditions:
|
|
15
16
|
* - plan.status must be 'writing'
|
|
16
17
|
* - plan.spec must be a non-empty string
|
|
17
18
|
*/
|
|
18
19
|
import { guild } from '@shardworks/nexus-core';
|
|
19
|
-
/**
|
|
20
|
-
* Parse the `<task-manifest>` block from the spec string.
|
|
21
|
-
* Returns the individual `<task ...>...</task>` fragments and the spec
|
|
22
|
-
* with the manifest block removed.
|
|
23
|
-
*
|
|
24
|
-
* If no manifest is found, returns null.
|
|
25
|
-
*/
|
|
26
|
-
export function parseTaskManifest(spec) {
|
|
27
|
-
// Match the full <task-manifest>...</task-manifest> block (greedy, single match)
|
|
28
|
-
const manifestMatch = spec.match(/<task-manifest>([\s\S]*?)<\/task-manifest>/);
|
|
29
|
-
if (!manifestMatch)
|
|
30
|
-
return null;
|
|
31
|
-
const manifestBlock = manifestMatch[0];
|
|
32
|
-
const manifestInner = manifestMatch[1];
|
|
33
|
-
// Extract individual <task ...>...</task> elements
|
|
34
|
-
const taskRegex = /<task\b[^>]*>[\s\S]*?<\/task>/g;
|
|
35
|
-
const tasks = [];
|
|
36
|
-
let match;
|
|
37
|
-
while ((match = taskRegex.exec(manifestInner)) !== null) {
|
|
38
|
-
tasks.push(match[0].trim());
|
|
39
|
-
}
|
|
40
|
-
if (tasks.length === 0)
|
|
41
|
-
return null;
|
|
42
|
-
// Strip the manifest block from the spec
|
|
43
|
-
const strippedSpec = spec.replace(manifestBlock, '').trim();
|
|
44
|
-
return { tasks, strippedSpec };
|
|
45
|
-
}
|
|
46
20
|
export function createSpecPublishEngine(getPlansBook) {
|
|
47
21
|
return {
|
|
48
22
|
id: 'astrolabe.spec-publish',
|
|
@@ -66,50 +40,7 @@ export function createSpecPublishEngine(getPlansBook) {
|
|
|
66
40
|
const briefWrit = await clerk.show(planId);
|
|
67
41
|
// Resolve generated writ type from config
|
|
68
42
|
const generatedWritType = guild().guildConfig().astrolabe?.generatedWritType ?? 'mandate';
|
|
69
|
-
//
|
|
70
|
-
const manifestResult = parseTaskManifest(plan.spec);
|
|
71
|
-
if (manifestResult) {
|
|
72
|
-
// ── Piece-aware path: manifest found ──
|
|
73
|
-
const { tasks, strippedSpec } = manifestResult;
|
|
74
|
-
// 1. Post mandate in draft state (pieces need parent to exist)
|
|
75
|
-
const generatedWrit = await clerk.post({
|
|
76
|
-
type: generatedWritType,
|
|
77
|
-
title: briefWrit.title,
|
|
78
|
-
body: strippedSpec,
|
|
79
|
-
codex: plan.codex,
|
|
80
|
-
draft: true,
|
|
81
|
-
});
|
|
82
|
-
// 2. Create child piece writs (one per task, in manifest order)
|
|
83
|
-
for (const taskXml of tasks) {
|
|
84
|
-
// Extract task id for a meaningful title
|
|
85
|
-
const idMatch = taskXml.match(/<task\s+id="([^"]+)"/);
|
|
86
|
-
const taskId = idMatch?.[1] ?? 'task';
|
|
87
|
-
const nameMatch = taskXml.match(/<name>([\s\S]*?)<\/name>/);
|
|
88
|
-
const taskName = nameMatch?.[1]?.trim() ?? taskId;
|
|
89
|
-
await clerk.post({
|
|
90
|
-
type: 'piece',
|
|
91
|
-
title: taskName,
|
|
92
|
-
body: taskXml,
|
|
93
|
-
parentId: generatedWrit.id,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
// 3. Link: mandate (source) → brief (target), type 'refines'
|
|
97
|
-
await clerk.link(generatedWrit.id, planId, 'refines');
|
|
98
|
-
// 4. Transition mandate from draft to open (ready for dispatch)
|
|
99
|
-
await clerk.transition(generatedWrit.id, 'open');
|
|
100
|
-
// 5. Update PlanDoc
|
|
101
|
-
const now = new Date().toISOString();
|
|
102
|
-
await book.patch(planId, {
|
|
103
|
-
generatedWritId: generatedWrit.id,
|
|
104
|
-
status: 'completed',
|
|
105
|
-
updatedAt: now,
|
|
106
|
-
});
|
|
107
|
-
return {
|
|
108
|
-
status: 'completed',
|
|
109
|
-
yields: { generatedWritId: generatedWrit.id },
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
// ── Legacy path: no manifest ──
|
|
43
|
+
// Post the mandate with the full spec verbatim (task-manifest included if present).
|
|
113
44
|
const generatedWrit = await clerk.post({
|
|
114
45
|
type: generatedWritType,
|
|
115
46
|
title: briefWrit.title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-publish.js","sourceRoot":"","sources":["../../src/engines/spec-publish.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spec-publish.js","sourceRoot":"","sources":["../../src/engines/spec-publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAM/C,MAAM,UAAU,uBAAuB,CAAC,YAAiC;IACvE,OAAO;QACL,EAAE,EAAE,wBAAwB;QAE5B,KAAK,CAAC,GAAG,CACP,MAA+B,EAC/B,QAA0B;YAE1B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAgB,CAAC;YACvC,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;YAE5B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,cAAc,CAAC,CAAC;YACjD,CAAC;YAED,kBAAkB;YAClB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CACb,yDAAyD,IAAI,CAAC,MAAM,eAAe,MAAM,IAAI,CAC9F,CAAC;YACJ,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC5D,MAAM,IAAI,KAAK,CACb,SAAS,MAAM,2DAA2D,CAC3E,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,KAAK,EAAE,CAAC,SAAS,CAAW,OAAO,CAAC,CAAC;YAEnD,oCAAoC;YACpC,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAE3C,0CAA0C;YAC1C,MAAM,iBAAiB,GAAG,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,iBAAiB,IAAI,SAAS,CAAC;YAE1F,oFAAoF;YACpF,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC;gBACrC,IAAI,EAAE,iBAAiB;gBACvB,KAAK,EAAE,SAAS,CAAC,KAAK;gBACtB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;aAClB,CAAC,CAAC;YAEH,0DAA0D;YAC1D,MAAM,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;YAEtD,iBAAiB;YACjB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACrC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;gBACvB,eAAe,EAAE,aAAa,CAAC,EAAE;gBACjC,MAAM,EAAE,WAAW;gBACnB,SAAS,EAAE,GAAG;aACf,CAAC,CAAC;YAEH,OAAO;gBACL,MAAM,EAAE,WAAW;gBACnB,MAAM,EAAE,EAAE,eAAe,EAAE,aAAa,CAAC,EAAE,EAAE;aAC9C,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardworks/astrolabe-apparatus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.214",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"zod": "4.3.6",
|
|
23
|
-
"@shardworks/stacks-apparatus": "0.1.
|
|
24
|
-
"@shardworks/
|
|
25
|
-
"@shardworks/
|
|
26
|
-
"@shardworks/
|
|
27
|
-
"@shardworks/
|
|
28
|
-
"@shardworks/
|
|
23
|
+
"@shardworks/stacks-apparatus": "0.1.214",
|
|
24
|
+
"@shardworks/clerk-apparatus": "0.1.214",
|
|
25
|
+
"@shardworks/tools-apparatus": "0.1.214",
|
|
26
|
+
"@shardworks/spider-apparatus": "0.1.214",
|
|
27
|
+
"@shardworks/fabricator-apparatus": "0.1.214",
|
|
28
|
+
"@shardworks/loom-apparatus": "0.1.214"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "25.5.0",
|
|
32
|
-
"@shardworks/nexus-core": "0.1.
|
|
32
|
+
"@shardworks/nexus-core": "0.1.214"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
package/sage-analyst.md
CHANGED
|
@@ -80,8 +80,9 @@ Not every brief produces decisions. If the existing codebase patterns truly dict
|
|
|
80
80
|
- What's the simplest version of this that a new operator would use on day one? Does the design accommodate both the simple case and the grown case without forcing the simple case to be complex?
|
|
81
81
|
|
|
82
82
|
5. **Classify the decision** (see Decision Analysis Metadata below).
|
|
83
|
-
6. **
|
|
84
|
-
7. **
|
|
83
|
+
6. **Pre-emption check — brief first.** Before applying the razor, check whether the brief (or an architecture spec it references) explicitly answers the question. If so, record the answer as both `recommendation` and `selected`, and cite the source in `rationale`. The patron has already decided; skip the razor.
|
|
84
|
+
7. **Apply the razor.** For any decision the brief did not pre-empt, check it against the five razor criteria in *The Razor* below. If it matches, leave `selected` unset so the decision surfaces to the patron. If it does not match, apply the three defaults — **investigate, don't punt:** uncertainty about a non-razor decision is a cue to read more code or re-read the brief, not a cue to hand the decision to the patron.
|
|
85
|
+
8. **Recommend.** Pick the best option. State why in one line. For auto-decided decisions, pre-fill `selected` with your choice.
|
|
85
86
|
|
|
86
87
|
**How to form recommendations:**
|
|
87
88
|
|
|
@@ -92,23 +93,25 @@ Not every brief produces decisions. If the existing codebase patterns truly dict
|
|
|
92
93
|
|
|
93
94
|
Not every decision warrants the patron's time. Over the last 38 specs the patron overrode only 3.7% of decisions — the rest were rubber-stamps. Most decisions can be settled by the analyst with a recorded recommendation, and only a narrow class should actually block on patron review.
|
|
94
95
|
|
|
95
|
-
**
|
|
96
|
+
**Pre-emption: the brief has the last word.** Before applying the razor, check whether the brief (or an architecture spec it references) explicitly answers the question — by prescribing a value, a behavior, or a pattern to follow. If so, pre-fill `selected` with that answer and cite the source in `rationale`, regardless of whether the question would otherwise match a razor criterion. The patron has already decided by writing the brief; re-surfacing a settled question drains attention from the decisions that are genuinely open.
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
2. **High consumer stakes.** A consumer of the API, feature, or workflow would materially notice the difference — ergonomics, runtime behavior, error semantics, performance. *Example:* "Should `post()` return the full writ or only the id?"
|
|
99
|
-
3. **Observable product surface.** An operator or end user would notice the choice in naming, UX, or behavior. *Example:* "Should the new page be titled `Reviews` or `Review History`?"
|
|
100
|
-
4. **Irreversibility.** The choice is hard to undo without a migration, a deprecation cycle, or breaking downstream consumers. *Example:* "Should the new field be stored as an index column or only in the document body?"
|
|
101
|
-
5. **Genuine ambiguity.** The codebase and the brief give no clear signal and two or more options are equally valid; no amount of further investigation would break the tie. *Example:* "Should we name this `digest` or `summary`? Both terms appear in the codebase with comparable frequency."
|
|
98
|
+
**Otherwise, surface the decision to the patron (leave `selected` unset) if — and only if — it falls into one of these five categories:**
|
|
102
99
|
|
|
103
|
-
**
|
|
100
|
+
1. **Vocabulary/pattern establishment.** New guild terms, categorical distinctions, or patterns other code will follow. *Example:* "Should we call the new state 'parked' or 'deferred'?"
|
|
101
|
+
2. **Human-facing surface.** CLI text, error messages, agent personalities, doc phrasing, or UX details a patron or operator will read. *Example:* "Should the error read 'Writ not found' or 'No writ with id X'?"
|
|
102
|
+
3. **Scope boundary.** Cutlines between the current commission and follow-up work — the 'should we also do X?' questions. *Example:* "Should this change also update the two-phase-planning rig, or is that a separate commission?"
|
|
103
|
+
4. **Shape of persisted or inter-component data.** Typed vs opaque, required vs optional, configured vs convention — when other components will consume the shape. *Example:* "Should `Decision.scope` be `string[]` or a typed `ScopeRef[]`?"
|
|
104
|
+
5. **Component responsibility boundaries.** Who owns a behavior across engines, tools, and apparatuses — when the decision sets a pattern for ownership. *Example:* "Should the sage write decisions through a tool, or through direct book access?"
|
|
105
|
+
|
|
106
|
+
**Investigate, don't punt.** When you feel uncertainty about a decision that does *not* match one of these five categories, that uncertainty is a signal to read more code, trace another caller, or re-read the brief — not a signal to surface the decision to the patron. Punting a non-razor decision drains patron attention from the decisions that actually need it.
|
|
104
107
|
|
|
105
108
|
#### The Three Defaults
|
|
106
109
|
|
|
107
|
-
For any decision that does **not** match the razor, apply these defaults
|
|
110
|
+
For any decision that was not pre-empted by the brief and does **not** match the razor, apply these defaults and pre-fill `selected` with the answer they produce:
|
|
108
111
|
|
|
109
|
-
1. **
|
|
110
|
-
2. **
|
|
111
|
-
3. **
|
|
112
|
+
1. **Prefer removal to deprecation.** When refactoring, rip out the old path. No deprecation windows unless the patron explicitly asks for one.
|
|
113
|
+
2. **Prefer fail-loud to silent fallback.** Throw on missing input; no defaults-when-absent unless the absent case is itself a legitimate state.
|
|
114
|
+
3. **Extend the API at the right layer; don't route around it.** If the recommendation involves a workaround or "the anima handles it via prompt," default to adding the method/tool instead.
|
|
112
115
|
|
|
113
116
|
Each decision needs:
|
|
114
117
|
- `id` — sequential identifier (D1, D2, ...)
|
|
@@ -118,7 +121,7 @@ Each decision needs:
|
|
|
118
121
|
- `options` — key → description map of reasonable approaches (keep descriptions to one line each)
|
|
119
122
|
- `recommendation` — the option key you recommend
|
|
120
123
|
- `rationale` — why this option, in one line
|
|
121
|
-
- `selected` — **If the decision matches any of the five razor criteria, leave `selected` unset
|
|
124
|
+
- `selected` — **If the brief pre-empts the decision, set `selected` to the brief's answer.** Otherwise, if the decision matches any of the five razor criteria, leave `selected` unset. Otherwise, apply the three defaults and pre-fill `selected` with your choice. Pre-filled decisions are auto-accepted — the engine drops them from the patron-review gate entirely, so the patron only sees decisions that genuinely warrant their attention. The patron changes `selected` only when overriding a surfaced decision, and if they write a custom override the reconcile loop replaces `selected` with `patronOverride` automatically. Never set both yourself.
|
|
122
125
|
- `analysis` — classification metadata (see below)
|
|
123
126
|
|
|
124
127
|
Order decisions by scope item, then by category (product → api → implementation).
|
package/sage-reading-analyst.md
CHANGED
|
@@ -115,8 +115,9 @@ Not every brief produces decisions. If the existing codebase patterns truly dict
|
|
|
115
115
|
- What's the simplest version of this that a new operator would use on day one? Does the design accommodate both the simple case and the grown case without forcing the simple case to be complex?
|
|
116
116
|
|
|
117
117
|
5. **Classify the decision** (see Decision Analysis Metadata below).
|
|
118
|
-
6. **
|
|
119
|
-
7. **
|
|
118
|
+
6. **Pre-emption check — brief first.** Before applying the razor, check whether the brief (or an architecture spec it references) explicitly answers the question. If so, record the answer as both `recommendation` and `selected`, and cite the source in `rationale`. The patron has already decided; skip the razor.
|
|
119
|
+
7. **Apply the razor.** For any decision the brief did not pre-empt, check it against the five razor criteria in *The Razor* below. If it matches, leave `selected` unset so the decision surfaces to the patron. If it does not match, apply the three defaults — **investigate, don't punt:** uncertainty about a non-razor decision is a cue to read more code or re-read the brief, not a cue to hand the decision to the patron.
|
|
120
|
+
8. **Recommend.** Pick the best option. State why in one line. For auto-decided decisions, pre-fill `selected` with your choice.
|
|
120
121
|
|
|
121
122
|
**How to form recommendations:**
|
|
122
123
|
|
|
@@ -127,23 +128,25 @@ Not every brief produces decisions. If the existing codebase patterns truly dict
|
|
|
127
128
|
|
|
128
129
|
Not every decision warrants the patron's time. Over the last 38 specs the patron overrode only 3.7% of decisions — the rest were rubber-stamps. Most decisions can be settled by the analyst with a recorded recommendation, and only a narrow class should actually block on patron review.
|
|
129
130
|
|
|
130
|
-
**
|
|
131
|
+
**Pre-emption: the brief has the last word.** Before applying the razor, check whether the brief (or an architecture spec it references) explicitly answers the question — by prescribing a value, a behavior, or a pattern to follow. If so, pre-fill `selected` with that answer and cite the source in `rationale`, regardless of whether the question would otherwise match a razor criterion. The patron has already decided by writing the brief; re-surfacing a settled question drains attention from the decisions that are genuinely open.
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
2. **High consumer stakes.** A consumer of the API, feature, or workflow would materially notice the difference — ergonomics, runtime behavior, error semantics, performance. *Example:* "Should `post()` return the full writ or only the id?"
|
|
134
|
-
3. **Observable product surface.** An operator or end user would notice the choice in naming, UX, or behavior. *Example:* "Should the new page be titled `Reviews` or `Review History`?"
|
|
135
|
-
4. **Irreversibility.** The choice is hard to undo without a migration, a deprecation cycle, or breaking downstream consumers. *Example:* "Should the new field be stored as an index column or only in the document body?"
|
|
136
|
-
5. **Genuine ambiguity.** The codebase and the brief give no clear signal and two or more options are equally valid; no amount of further investigation would break the tie. *Example:* "Should we name this `digest` or `summary`? Both terms appear in the codebase with comparable frequency."
|
|
133
|
+
**Otherwise, surface the decision to the patron (leave `selected` unset) if — and only if — it falls into one of these five categories:**
|
|
137
134
|
|
|
138
|
-
**
|
|
135
|
+
1. **Vocabulary/pattern establishment.** New guild terms, categorical distinctions, or patterns other code will follow. *Example:* "Should we call the new state 'parked' or 'deferred'?"
|
|
136
|
+
2. **Human-facing surface.** CLI text, error messages, agent personalities, doc phrasing, or UX details a patron or operator will read. *Example:* "Should the error read 'Writ not found' or 'No writ with id X'?"
|
|
137
|
+
3. **Scope boundary.** Cutlines between the current commission and follow-up work — the 'should we also do X?' questions. *Example:* "Should this change also update the two-phase-planning rig, or is that a separate commission?"
|
|
138
|
+
4. **Shape of persisted or inter-component data.** Typed vs opaque, required vs optional, configured vs convention — when other components will consume the shape. *Example:* "Should `Decision.scope` be `string[]` or a typed `ScopeRef[]`?"
|
|
139
|
+
5. **Component responsibility boundaries.** Who owns a behavior across engines, tools, and apparatuses — when the decision sets a pattern for ownership. *Example:* "Should the sage write decisions through a tool, or through direct book access?"
|
|
140
|
+
|
|
141
|
+
**Investigate, don't punt.** When you feel uncertainty about a decision that does *not* match one of these five categories, that uncertainty is a signal to read more code, trace another caller, or re-read the brief — not a signal to surface the decision to the patron. Punting a non-razor decision drains patron attention from the decisions that actually need it.
|
|
139
142
|
|
|
140
143
|
#### The Three Defaults
|
|
141
144
|
|
|
142
|
-
For any decision that does **not** match the razor, apply these defaults
|
|
145
|
+
For any decision that was not pre-empted by the brief and does **not** match the razor, apply these defaults and pre-fill `selected` with the answer they produce:
|
|
143
146
|
|
|
144
|
-
1. **
|
|
145
|
-
2. **
|
|
146
|
-
3. **
|
|
147
|
+
1. **Prefer removal to deprecation.** When refactoring, rip out the old path. No deprecation windows unless the patron explicitly asks for one.
|
|
148
|
+
2. **Prefer fail-loud to silent fallback.** Throw on missing input; no defaults-when-absent unless the absent case is itself a legitimate state.
|
|
149
|
+
3. **Extend the API at the right layer; don't route around it.** If the recommendation involves a workaround or "the anima handles it via prompt," default to adding the method/tool instead.
|
|
147
150
|
|
|
148
151
|
Each decision needs:
|
|
149
152
|
- `id` — sequential identifier (D1, D2, ...)
|
|
@@ -153,7 +156,7 @@ Each decision needs:
|
|
|
153
156
|
- `options` — key → description map of reasonable approaches (keep descriptions to one line each)
|
|
154
157
|
- `recommendation` — the option key you recommend
|
|
155
158
|
- `rationale` — why this option, in one line
|
|
156
|
-
- `selected` — **If the decision matches any of the five razor criteria, leave `selected` unset
|
|
159
|
+
- `selected` — **If the brief pre-empts the decision, set `selected` to the brief's answer.** Otherwise, if the decision matches any of the five razor criteria, leave `selected` unset. Otherwise, apply the three defaults and pre-fill `selected` with your choice. Pre-filled decisions are auto-accepted — the engine drops them from the patron-review gate entirely, so the patron only sees decisions that genuinely warrant their attention. The patron changes `selected` only when overriding a surfaced decision, and if they write a custom override the reconcile loop replaces `selected` with `patronOverride` automatically. Never set both yourself.
|
|
157
160
|
- `analysis` — classification metadata (see below)
|
|
158
161
|
|
|
159
162
|
Order decisions by scope item, then by category (product → api → implementation).
|