yaaw-se 0.3.9 → 0.3.11-dev.36385821931.1
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/dist/payload/payload-files.json +33 -28
- package/dist/payload/payload.json +1 -1
- package/dist/payload/skills/yaaw-orchestrator/SKILL.md +1 -1
- package/dist/payload/yaaw-core/system/core/lifecycle.md +3 -1
- package/dist/payload/yaaw-core/system/core/state-model.md +1 -1
- package/dist/payload/yaaw-core/system/registries/handoff-policy.json +2 -2
- package/dist/payload/yaaw-core/system/registries/reconciliation-policy.json +7 -6
- package/dist/payload/yaaw-core/system/registries/routing-policy.json +1 -0
- package/dist/payload/yaaw-core/system/tools/frontmatter.mjs +173 -0
- package/dist/payload/yaaw-core/system/tools/orchestration-engine.mjs +21 -6
- package/dist/payload/yaaw-core/system/tools/orchestration-runtime.mjs +3 -4
- package/dist/payload/yaaw-core/system/workflows/orchestration/determine-next-action.md +1 -1
- package/dist/payload/yaaw-core/system/workflows/orchestration/recover-interruption.md +17 -3
- package/dist/payload/yaaw-core/system/workflows/orchestration/route.md +2 -2
- package/dist/payload/yaaw-core/system/workflows/planning/create-tickets.md +38 -11
- package/dist/payload/yaaw-core/system/workflows/planning/route.md +3 -2
- package/package.json +1 -1
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "payload.json",
|
|
31
|
-
"sha256": "
|
|
32
|
-
"bytes":
|
|
31
|
+
"sha256": "e6777f48978f169c8951edfb209cfd1978a370d281c0ea0ba5f5ec1142f17433",
|
|
32
|
+
"bytes": 75
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "skills/yaaw-create-spec/SKILL.md",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "skills/yaaw-orchestrator/SKILL.md",
|
|
56
|
-
"sha256": "
|
|
57
|
-
"bytes":
|
|
56
|
+
"sha256": "981e912d418cd833e8281034f739e206894134a496766caacb97471b9577ec1c",
|
|
57
|
+
"bytes": 864
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
"path": "skills/yaaw-planner/SKILL.md",
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"path": "yaaw-core/system/core/lifecycle.md",
|
|
136
|
-
"sha256": "
|
|
137
|
-
"bytes":
|
|
136
|
+
"sha256": "c1d089198c96b3f1f2c5c8a3b4c98ba5f9a429e1d99d15dde05f4b9807fb54f8",
|
|
137
|
+
"bytes": 1224
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"path": "yaaw-core/system/core/recovery.md",
|
|
@@ -148,8 +148,8 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"path": "yaaw-core/system/core/state-model.md",
|
|
151
|
-
"sha256": "
|
|
152
|
-
"bytes":
|
|
151
|
+
"sha256": "203c3b5a12d65cdcec3a47bc25b40607e0b1733c99a4e43449f6042a6574172e",
|
|
152
|
+
"bytes": 1224
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"path": "yaaw-core/system/core/transitions.md",
|
|
@@ -198,8 +198,8 @@
|
|
|
198
198
|
},
|
|
199
199
|
{
|
|
200
200
|
"path": "yaaw-core/system/registries/handoff-policy.json",
|
|
201
|
-
"sha256": "
|
|
202
|
-
"bytes":
|
|
201
|
+
"sha256": "a679a166bfb30349eec16602d5e0df366df4f6176246b578d7967e25d123efdb",
|
|
202
|
+
"bytes": 4921
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
"path": "yaaw-core/system/registries/paths.json",
|
|
@@ -208,8 +208,8 @@
|
|
|
208
208
|
},
|
|
209
209
|
{
|
|
210
210
|
"path": "yaaw-core/system/registries/reconciliation-policy.json",
|
|
211
|
-
"sha256": "
|
|
212
|
-
"bytes":
|
|
211
|
+
"sha256": "795745109cc165d806cfe3d43d5836077f962b910faf3e8ba3a8396f74e9b7d2",
|
|
212
|
+
"bytes": 745
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"path": "yaaw-core/system/registries/role-io.json",
|
|
@@ -218,8 +218,8 @@
|
|
|
218
218
|
},
|
|
219
219
|
{
|
|
220
220
|
"path": "yaaw-core/system/registries/routing-policy.json",
|
|
221
|
-
"sha256": "
|
|
222
|
-
"bytes":
|
|
221
|
+
"sha256": "8a8bb2dd65bbd8338081fdd275042a35fac1a70b52d0eacb62d24f15bae59499",
|
|
222
|
+
"bytes": 2008
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
"path": "yaaw-core/system/registries/skills.json",
|
|
@@ -486,15 +486,20 @@
|
|
|
486
486
|
"sha256": "27e76946abf1abde306a561621f5a7833a164f3af85db83f5db22b6809450ba6",
|
|
487
487
|
"bytes": 4812
|
|
488
488
|
},
|
|
489
|
+
{
|
|
490
|
+
"path": "yaaw-core/system/tools/frontmatter.mjs",
|
|
491
|
+
"sha256": "2c90097563a91c52e6f9c7748dac583e67967fd4a9748e393bf61d753594277e",
|
|
492
|
+
"bytes": 5474
|
|
493
|
+
},
|
|
489
494
|
{
|
|
490
495
|
"path": "yaaw-core/system/tools/orchestration-engine.mjs",
|
|
491
|
-
"sha256": "
|
|
492
|
-
"bytes":
|
|
496
|
+
"sha256": "b14d2f6d0c25246aab9a2c84adaad4a20b41d1857755725b145cde48cda4123b",
|
|
497
|
+
"bytes": 20364
|
|
493
498
|
},
|
|
494
499
|
{
|
|
495
500
|
"path": "yaaw-core/system/tools/orchestration-runtime.mjs",
|
|
496
|
-
"sha256": "
|
|
497
|
-
"bytes":
|
|
501
|
+
"sha256": "e25923b994867d5dea2c6e9a7391ecd66371a042ee95a1ccc1553f963464efc7",
|
|
502
|
+
"bytes": 13540
|
|
498
503
|
},
|
|
499
504
|
{
|
|
500
505
|
"path": "yaaw-core/system/tools/repository-identity.mjs",
|
|
@@ -523,8 +528,8 @@
|
|
|
523
528
|
},
|
|
524
529
|
{
|
|
525
530
|
"path": "yaaw-core/system/workflows/orchestration/determine-next-action.md",
|
|
526
|
-
"sha256": "
|
|
527
|
-
"bytes":
|
|
531
|
+
"sha256": "69ca31d6323b0b1d10a8bd4b17423b8f4c81df095f495c29c0231ff609fa9eb0",
|
|
532
|
+
"bytes": 1407
|
|
528
533
|
},
|
|
529
534
|
{
|
|
530
535
|
"path": "yaaw-core/system/workflows/orchestration/dispatch.md",
|
|
@@ -543,13 +548,13 @@
|
|
|
543
548
|
},
|
|
544
549
|
{
|
|
545
550
|
"path": "yaaw-core/system/workflows/orchestration/recover-interruption.md",
|
|
546
|
-
"sha256": "
|
|
547
|
-
"bytes":
|
|
551
|
+
"sha256": "a8a692aa1461d7230ecb4b7d05c0d258b45a3956930c34dd9d38dadf7490660d",
|
|
552
|
+
"bytes": 2147
|
|
548
553
|
},
|
|
549
554
|
{
|
|
550
555
|
"path": "yaaw-core/system/workflows/orchestration/route.md",
|
|
551
|
-
"sha256": "
|
|
552
|
-
"bytes":
|
|
556
|
+
"sha256": "5965bf77dc93b8ad89a177fbf06e3465b14ba41ba9f47f3e440a7884c9a777a5",
|
|
557
|
+
"bytes": 1353
|
|
553
558
|
},
|
|
554
559
|
{
|
|
555
560
|
"path": "yaaw-core/system/workflows/planning/create-spec.md",
|
|
@@ -558,8 +563,8 @@
|
|
|
558
563
|
},
|
|
559
564
|
{
|
|
560
565
|
"path": "yaaw-core/system/workflows/planning/create-tickets.md",
|
|
561
|
-
"sha256": "
|
|
562
|
-
"bytes":
|
|
566
|
+
"sha256": "b868389870177c13cccb76635ea6f057b53164861b1a7090de532c1b8ac7aa0d",
|
|
567
|
+
"bytes": 4028
|
|
563
568
|
},
|
|
564
569
|
{
|
|
565
570
|
"path": "yaaw-core/system/workflows/planning/decision-frontier.md",
|
|
@@ -598,8 +603,8 @@
|
|
|
598
603
|
},
|
|
599
604
|
{
|
|
600
605
|
"path": "yaaw-core/system/workflows/planning/route.md",
|
|
601
|
-
"sha256": "
|
|
602
|
-
"bytes":
|
|
606
|
+
"sha256": "fa3bf9bc6c47868f7e2d85235669e73065bdbcdb4a7812304ea37f6989641449",
|
|
607
|
+
"bytes": 1710
|
|
603
608
|
},
|
|
604
609
|
{
|
|
605
610
|
"path": "yaaw-core/system/workflows/planning/write-understanding.md",
|
|
@@ -11,4 +11,4 @@ INTENT: `CONTINUE`
|
|
|
11
11
|
Resolve the YAAW workspace root, then invoke:
|
|
12
12
|
`node .yaaw-core/system/tools/orchestration-runtime.mjs --workspace <WORKSPACE_ROOT> --invoke-skill yaaw-orchestrator`
|
|
13
13
|
|
|
14
|
-
Follow `orchestration.route` until `CONTINUE_UNTIL_STOP` is satisfied or a human-input, BLOCKED, or framework stop occurs. Do not execute orchestrator semantics directly from this wrapper; every semantic execution requires the exact runtime handoff.
|
|
14
|
+
Follow `orchestration.route` until `CONTINUE_UNTIL_STOP` is satisfied or a human-input, BLOCKED, or framework stop occurs. A `FRAMEWORK_STOP` is terminal for the current invocation: report it once and do not re-run the unchanged runtime unless the installation version, manifest, or managed framework bytes have changed. Do not execute orchestrator semantics directly from this wrapper; every semantic execution requires the exact runtime handoff.
|
|
@@ -7,6 +7,8 @@ product missing/unready -> PRD
|
|
|
7
7
|
product ready, planning unresolved -> Planner
|
|
8
8
|
planning ready, spec missing -> create/adopt spec
|
|
9
9
|
spec accepted, tickets missing -> create/register tickets
|
|
10
|
+
registered DRAFT + dependencies PASS -> Planner re-evaluates admission
|
|
11
|
+
Planner persists current DRAFT -> READY -> Orchestrator adopts TICKET_ADMISSION
|
|
10
12
|
READY -> implementation_start fact -> IN_PROGRESS
|
|
11
13
|
IN_PROGRESS -> PASS verification fact -> REVIEW_REQUIRED
|
|
12
14
|
REVIEW_REQUIRED -> immutable Reviewer result -> PASS | REPAIR_REQUIRED | REPLAN_REQUIRED | BLOCKED
|
|
@@ -15,7 +17,7 @@ PASS + scope_status OPEN/UNKNOWN -> Planner
|
|
|
15
17
|
all current tickets PASS/CANCELLED + scope_status COMPLETE -> COMPLETE
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
One reconciliation is applied per observation cycle; recovery never jumps directly from `READY` to `REVIEW_REQUIRED`.
|
|
20
|
+
One reconciliation is applied per observation cycle; recovery never jumps directly from `READY` to `REVIEW_REQUIRED`. Recovery is interruption-only and must not be used as the fallback for a current `DRAFT` ticket or missing planning admission.
|
|
19
21
|
|
|
20
22
|
## Fresh-context invariant
|
|
21
23
|
Conversation may disappear at any point. Durable artifacts and repository evidence must still identify the correct next boundary.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Canonical project state is `.yaaw-core/project/state.json` using `yaaw.project-state/v2`.
|
|
4
4
|
|
|
5
|
-
State is a routing/adoption ledger, not semantic authority. Ticket frontmatter is admission/history metadata and never overrides the reconciled ticket lifecycle value in state.
|
|
5
|
+
State is a routing/adoption ledger, not semantic authority. Ticket frontmatter is admission/history metadata and never directly overrides the reconciled ticket lifecycle value in state. A Planner-owned `DRAFT -> READY` artifact change is instead a candidate durable admission fact: Orchestrator validates current sources, planning readiness, dependency completion, repository identity, and the legal transition before adopting it as `TICKET_ADMISSION`.
|
|
6
6
|
|
|
7
7
|
## State writer
|
|
8
8
|
Orchestrator is the only physical writer of `state.json`. Semantic roles create durable facts: Planner creates engineering/spec/ticket facts, Implementer creates start/verification evidence, Reviewer creates immutable review results. Orchestrator validates and adopts those facts through legal transitions.
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
},
|
|
24
24
|
"planning.create-tickets": {
|
|
25
25
|
"desired_intent": "CREATE_TICKETS", "active_artifact": "spec",
|
|
26
|
-
"reads": ["product","engineering","engineering_research","spec","project_rule","repository","state"], "writes": ["ticket"],
|
|
26
|
+
"reads": ["product","engineering","engineering_research","spec","ticket","project_rule","repository","state"], "writes": ["ticket"],
|
|
27
27
|
"required_expertise": [], "ticket_expertise": false,
|
|
28
|
-
"expected_output": "Dependency-aware
|
|
28
|
+
"expected_output": "Dependency-aware tickets for the accepted specification, including re-evaluation of current DRAFT admission candidates, or a typed prerequisite/stop result.",
|
|
29
29
|
"result_vocabulary": ["SUCCESS","PRECONDITION_UNSATISFIED","BLOCKED"]
|
|
30
30
|
},
|
|
31
31
|
"planning.replan": {
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
{"id": "PLANNING_LEDGER_SYNC", "priority": 3},
|
|
7
7
|
{"id": "ACTIVE_SPEC_UNADOPTED", "priority": 4},
|
|
8
8
|
{"id": "TICKET_UNREGISTERED", "priority": 5},
|
|
9
|
-
{"id": "
|
|
10
|
-
{"id": "
|
|
11
|
-
{"id": "
|
|
12
|
-
{"id": "
|
|
13
|
-
{"id": "
|
|
14
|
-
{"id": "
|
|
9
|
+
{"id": "TICKET_ADMISSION", "priority": 6},
|
|
10
|
+
{"id": "CONTRACT_SOURCE_STALE", "priority": 7},
|
|
11
|
+
{"id": "IMPLEMENTATION_STARTED", "priority": 8},
|
|
12
|
+
{"id": "IMPLEMENTATION_VERIFIED", "priority": 9},
|
|
13
|
+
{"id": "REVIEW_RESULT_UNAPPLIED", "priority": 10},
|
|
14
|
+
{"id": "ACCEPTANCE_STALE", "priority": 11},
|
|
15
|
+
{"id": "PROJECT_COMPLETION_SYNC", "priority": 12}
|
|
15
16
|
],
|
|
16
17
|
"one_reconciliation_per_cycle": true
|
|
17
18
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"planning_unready_workflow": "planning.route",
|
|
5
5
|
"missing_spec_workflow": "planning.create-spec",
|
|
6
6
|
"missing_tickets_workflow": "planning.create-tickets",
|
|
7
|
+
"draft_admission_workflow": "planning.create-tickets",
|
|
7
8
|
"next_frontier_workflow": "planning.route",
|
|
8
9
|
"complete_terminal": "COMPLETE",
|
|
9
10
|
"ticket_state_precedence": [
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
function indentOf(line) {
|
|
2
|
+
const match = String(line).match(/^ */);
|
|
3
|
+
return match ? match[0].length : 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function unquote(raw) {
|
|
7
|
+
if (raw.length >= 2 && raw.startsWith("\"") && raw.endsWith("\"")) {
|
|
8
|
+
try { return JSON.parse(raw); } catch { return raw.slice(1, -1); }
|
|
9
|
+
}
|
|
10
|
+
if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) {
|
|
11
|
+
return raw.slice(1, -1).replace(/''/g, "'");
|
|
12
|
+
}
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function splitInline(raw) {
|
|
17
|
+
const out = [];
|
|
18
|
+
let start = 0, quote = null, depth = 0;
|
|
19
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
20
|
+
const ch = raw[i];
|
|
21
|
+
if (quote) {
|
|
22
|
+
if (ch === quote && raw[i - 1] !== "\\") quote = null;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (ch === "\"" || ch === "'") { quote = ch; continue; }
|
|
26
|
+
if (ch === "[" || ch === "{") depth += 1;
|
|
27
|
+
else if (ch === "]" || ch === "}") depth -= 1;
|
|
28
|
+
else if (ch === "," && depth === 0) {
|
|
29
|
+
out.push(raw.slice(start, i));
|
|
30
|
+
start = i + 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
out.push(raw.slice(start));
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseScalar(value) {
|
|
38
|
+
const raw = String(value ?? "").trim();
|
|
39
|
+
if (raw === "") return "";
|
|
40
|
+
if (raw === "null" || raw === "~") return null;
|
|
41
|
+
if (raw === "true") return true;
|
|
42
|
+
if (raw === "false") return false;
|
|
43
|
+
if (/^-?\d+$/.test(raw)) return Number(raw);
|
|
44
|
+
if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
45
|
+
try { return JSON.parse(raw); } catch {}
|
|
46
|
+
const body = raw.slice(1, -1).trim();
|
|
47
|
+
if (!body) return [];
|
|
48
|
+
return splitInline(body).map(item => parseScalar(item));
|
|
49
|
+
}
|
|
50
|
+
if (raw.startsWith("{") && raw.endsWith("}")) {
|
|
51
|
+
try { return JSON.parse(raw); } catch {}
|
|
52
|
+
}
|
|
53
|
+
return unquote(raw);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function nextContent(lines, index, end) {
|
|
57
|
+
for (let i = index; i < end; i += 1) {
|
|
58
|
+
const trimmed = lines[i].trim();
|
|
59
|
+
if (trimmed && !trimmed.startsWith("#")) return i;
|
|
60
|
+
}
|
|
61
|
+
return end;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function keyValue(text) {
|
|
65
|
+
const index = text.indexOf(":");
|
|
66
|
+
if (index <= 0) return null;
|
|
67
|
+
return [text.slice(0, index).trim(), text.slice(index + 1).trim()];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseMapping(lines, start, end, indent) {
|
|
71
|
+
const out = {};
|
|
72
|
+
let i = start;
|
|
73
|
+
while (i < end) {
|
|
74
|
+
i = nextContent(lines, i, end);
|
|
75
|
+
if (i >= end) break;
|
|
76
|
+
const raw = lines[i], current = indentOf(raw), trimmed = raw.trim();
|
|
77
|
+
if (current < indent) break;
|
|
78
|
+
if (current > indent) throw new Error(`unexpected indentation at frontmatter line ${i + 1}`);
|
|
79
|
+
if (trimmed.startsWith("-")) break;
|
|
80
|
+
const pair = keyValue(trimmed);
|
|
81
|
+
if (!pair) throw new Error(`invalid frontmatter mapping at line ${i + 1}`);
|
|
82
|
+
const [key, rest] = pair;
|
|
83
|
+
if (rest !== "") {
|
|
84
|
+
out[key] = parseScalar(rest);
|
|
85
|
+
i += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const next = nextContent(lines, i + 1, end);
|
|
89
|
+
if (next >= end || indentOf(lines[next]) <= indent) {
|
|
90
|
+
out[key] = {};
|
|
91
|
+
i += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const [child, after] = parseNode(lines, next, end, indentOf(lines[next]));
|
|
95
|
+
out[key] = child;
|
|
96
|
+
i = after;
|
|
97
|
+
}
|
|
98
|
+
return [out, i];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseSequence(lines, start, end, indent) {
|
|
102
|
+
const out = [];
|
|
103
|
+
let i = start;
|
|
104
|
+
while (i < end) {
|
|
105
|
+
i = nextContent(lines, i, end);
|
|
106
|
+
if (i >= end) break;
|
|
107
|
+
const raw = lines[i], current = indentOf(raw), trimmed = raw.trim();
|
|
108
|
+
if (current < indent) break;
|
|
109
|
+
if (current > indent) throw new Error(`unexpected indentation at frontmatter line ${i + 1}`);
|
|
110
|
+
if (!trimmed.startsWith("-")) break;
|
|
111
|
+
const body = trimmed.slice(1).trim();
|
|
112
|
+
if (body === "") {
|
|
113
|
+
const next = nextContent(lines, i + 1, end);
|
|
114
|
+
if (next >= end || indentOf(lines[next]) <= indent) {
|
|
115
|
+
out.push(null);
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const [child, after] = parseNode(lines, next, end, indentOf(lines[next]));
|
|
120
|
+
out.push(child);
|
|
121
|
+
i = after;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const pair = keyValue(body);
|
|
125
|
+
if (pair) {
|
|
126
|
+
const item = {};
|
|
127
|
+
const [key, rest] = pair;
|
|
128
|
+
item[key] = rest === "" ? {} : parseScalar(rest);
|
|
129
|
+
const next = nextContent(lines, i + 1, end);
|
|
130
|
+
if (next < end && indentOf(lines[next]) > indent) {
|
|
131
|
+
const childIndent = indentOf(lines[next]);
|
|
132
|
+
if (rest === "") {
|
|
133
|
+
const [child, after] = parseNode(lines, next, end, childIndent);
|
|
134
|
+
item[key] = child;
|
|
135
|
+
i = after;
|
|
136
|
+
} else {
|
|
137
|
+
const [extra, after] = parseMapping(lines, next, end, childIndent);
|
|
138
|
+
Object.assign(item, extra);
|
|
139
|
+
i = after;
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
i += 1;
|
|
143
|
+
}
|
|
144
|
+
out.push(item);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
out.push(parseScalar(body));
|
|
148
|
+
i += 1;
|
|
149
|
+
}
|
|
150
|
+
return [out, i];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseNode(lines, start, end, indent) {
|
|
154
|
+
const index = nextContent(lines, start, end);
|
|
155
|
+
if (index >= end) return [{}, end];
|
|
156
|
+
return lines[index].trim().startsWith("-")
|
|
157
|
+
? parseSequence(lines, index, end, indent)
|
|
158
|
+
: parseMapping(lines, index, end, indent);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function parseFrontmatter(text) {
|
|
162
|
+
const lines = String(text).split(/\r?\n/);
|
|
163
|
+
if (lines[0]?.trim() !== "---") throw new Error("missing opening frontmatter");
|
|
164
|
+
const relativeEnd = lines.slice(1).findIndex(line => line.trim() === "---");
|
|
165
|
+
if (relativeEnd < 0) throw new Error("missing closing frontmatter");
|
|
166
|
+
const end = relativeEnd + 1;
|
|
167
|
+
const start = nextContent(lines, 1, end);
|
|
168
|
+
if (start >= end) return {};
|
|
169
|
+
const [value, after] = parseMapping(lines, start, end, indentOf(lines[start]));
|
|
170
|
+
const trailing = nextContent(lines, after, end);
|
|
171
|
+
if (trailing < end) throw new Error(`unsupported frontmatter syntax at line ${trailing + 1}`);
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
@@ -31,7 +31,7 @@ function transition(id,subject,from,to,reason,evidence=[]){return{id,kind:"STATE
|
|
|
31
31
|
function currentTicketIds(state,a){const s=activeSpec(state,a);return s?Object.keys(state?.tickets??{}).filter(id=>ticketSourceStatus(a?.tickets?.[id]?.meta,state,s)==="CURRENT").sort():[];}
|
|
32
32
|
|
|
33
33
|
export function selectReconciliation({state,artifacts:a,evidence=[],reviews=[],repository,transitions,reconciliationPolicy,routingPolicy}){
|
|
34
|
-
const order=["PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED","PRODUCT_LEDGER_SYNC","PLANNING_LEDGER_SYNC","ACTIVE_SPEC_UNADOPTED","TICKET_UNREGISTERED","CONTRACT_SOURCE_STALE","IMPLEMENTATION_STARTED","IMPLEMENTATION_VERIFIED","REVIEW_RESULT_UNAPPLIED","ACCEPTANCE_STALE","PROJECT_COMPLETION_SYNC"];
|
|
34
|
+
const order=["PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED","PRODUCT_LEDGER_SYNC","PLANNING_LEDGER_SYNC","ACTIVE_SPEC_UNADOPTED","TICKET_UNREGISTERED","TICKET_ADMISSION","CONTRACT_SOURCE_STALE","IMPLEMENTATION_STARTED","IMPLEMENTATION_VERIFIED","REVIEW_RESULT_UNAPPLIED","ACCEPTANCE_STALE","PROJECT_COMPLETION_SYNC"];
|
|
35
35
|
const declared=(reconciliationPolicy?.priority??[]).map(x=>typeof x==="string"?x:x.id);
|
|
36
36
|
if(declared.length&&JSON.stringify(declared)!==JSON.stringify(order))return{id:"RECONCILIATION_POLICY_DRIFT",kind:"BLOCKED",reason:"reconciliation priority differs from engine contract"};
|
|
37
37
|
if(!state)return{id:"PROJECT_STATE_MISSING",kind:"BLOCKED",reason:"project state missing"};
|
|
@@ -65,6 +65,17 @@ export function selectReconciliation({state,artifacts:a,evidence=[],reviews=[],r
|
|
|
65
65
|
if(ticketSourceStatus(x.meta,state,s)!=="CURRENT")continue;
|
|
66
66
|
return{id:"TICKET_UNREGISTERED",kind:"STATE",subject:id,from:null,to:x.meta.status,reason:"current ticket artifact not registered",workflow:"orchestration.reconcile-state",evidence:[x.path],patch:{ticket:id,status:x.meta.status}};
|
|
67
67
|
}
|
|
68
|
+
for(const id of Object.keys(state.tickets??{}).sort()){
|
|
69
|
+
const st=state.tickets[id],m=a?.tickets?.[id]?.meta;
|
|
70
|
+
if(st!=="DRAFT"||String(m?.status??"")!=="READY")continue;
|
|
71
|
+
if(ticketSourceStatus(m,state,s)!=="CURRENT")continue;
|
|
72
|
+
if(normalizeStatus(state?.planning?.status)!=="ready"||state?.planning?.readiness!=="PASS")continue;
|
|
73
|
+
if(!deps(id,a,state)||repository?.status!=="READY")continue;
|
|
74
|
+
const contract=(transitions?.legal??[]).find(x=>x.from==="DRAFT"&&x.to==="READY");
|
|
75
|
+
if(contract?.owner!=="planner"||contract?.workflow!=="planning.create-tickets")
|
|
76
|
+
return{id:"ILLEGAL_TICKET_ADMISSION_CONTRACT",kind:"BLOCKED",reason:"DRAFT -> READY admission must be owned by Planner planning.create-tickets"};
|
|
77
|
+
return transition("TICKET_ADMISSION",id,"DRAFT","READY","PLANNER_ADMISSION_CURRENT",[a.tickets[id].path,s.path]);
|
|
78
|
+
}
|
|
68
79
|
for(const id of Object.keys(state.tickets??{}).sort()){
|
|
69
80
|
const st=state.tickets[id];if(!EXECUTABLE.has(st))continue;
|
|
70
81
|
const cause=ticketSourceStatus(a?.tickets?.[id]?.meta,state,s);
|
|
@@ -143,9 +154,13 @@ function baseRoute(state,a,evidence,reviews,repository,p){
|
|
|
143
154
|
}
|
|
144
155
|
const ip=ids.find(x=>tickets[x]==="IN_PROGRESS");if(ip){const m=a.tickets[ip].meta;return start(evidence,ip,Number(m.revision),Number(s.revision))?{kind:"DISPATCH_READY",workflow:"implementation.verify-ticket",ticket:ip}:{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",ticket:ip,reason:"IMPLEMENTATION_START_EVIDENCE_MISSING"};}
|
|
145
156
|
const ready=ids.find(x=>tickets[x]==="READY"&&deps(x,a,state));if(ready)return{kind:"DISPATCH_READY",workflow:"implementation.implement-ticket",ticket:ready};
|
|
157
|
+
const draft=ids.find(x=>tickets[x]==="DRAFT"&&deps(x,a,state));if(draft)return{kind:"DISPATCH_READY",workflow:p.draft_admission_workflow??"planning.create-tickets",ticket:draft,reason:"TICKET_ADMISSION_REQUIRED"};
|
|
146
158
|
const vals=ids.map(x=>tickets[x]);if(vals.some(x=>x==="BLOCKED"))return{kind:"BLOCKED",terminal:"BLOCKED",reason:"TICKET_BLOCKED"};
|
|
147
159
|
if(vals.length&&vals.every(x=>x==="PASS"||x==="CANCELLED"))return state.planning.scope_status==="COMPLETE"?{kind:"TERMINAL",terminal:p.complete_terminal}:{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow};
|
|
148
|
-
return{kind:"
|
|
160
|
+
if(vals.some(x=>x==="DRAFT"))return{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow,reason:"DRAFT_DEPENDENCIES_UNSATISFIED"};
|
|
161
|
+
const unsupported=vals.find(x=>!["READY","IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","REPLAN_REQUIRED","BLOCKED","PASS","CANCELLED","DRAFT"].includes(x));
|
|
162
|
+
if(unsupported)return{kind:"BLOCKED",terminal:"BLOCKED",reason:"UNSUPPORTED_TICKET_STATE:"+unsupported};
|
|
163
|
+
return{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow,reason:"PLANNING_ROUTE_REQUIRED"};
|
|
149
164
|
}
|
|
150
165
|
function legalNow(w,state,a){
|
|
151
166
|
const s=activeSpec(state,a),t=state?.tickets??{},productReady=normalizeStatus(state?.product?.status)==="ready";
|
|
@@ -153,7 +168,7 @@ function legalNow(w,state,a){
|
|
|
153
168
|
if(["prd.revise","prd.refine"].includes(w))return Boolean(a?.product?.meta);
|
|
154
169
|
if(["planning.route","planning.readiness-review"].includes(w))return productReady;
|
|
155
170
|
if(w==="planning.create-spec")return productReady&&normalizeStatus(state.planning.status)==="ready"&&state.planning.readiness==="PASS"&&!s;
|
|
156
|
-
if(w==="planning.create-tickets")return Boolean(s)&&
|
|
171
|
+
if(w==="planning.create-tickets"){const ids=currentTicketIds(state,a);return Boolean(s)&&(ids.length===0||ids.some(id=>t[id]==="DRAFT"&&deps(id,a,state)));}
|
|
157
172
|
if(w==="implementation.implement-ticket")return Object.values(t).includes("READY");
|
|
158
173
|
if(w==="implementation.repair-ticket")return Object.values(t).includes("REPAIR_REQUIRED");
|
|
159
174
|
if(w==="review.review-ticket")return Object.values(t).includes("REVIEW_REQUIRED");
|
|
@@ -162,7 +177,7 @@ function legalNow(w,state,a){
|
|
|
162
177
|
export function intentComplete(i,state,a){
|
|
163
178
|
if(!i)return false;const target=String(i.target_artifact??"").match(/TASK-[0-9]+/)?.[0]??null,o=i.desired_outcome,advanced=Number(state?.last_transition?.sequence??0)>Number(i.created_from_transition_sequence??0);
|
|
164
179
|
if(o==="CREATE_SPEC")return currentAcceptedSpecs(state,a).length===1;
|
|
165
|
-
if(o==="CREATE_TICKETS")
|
|
180
|
+
if(o==="CREATE_TICKETS"){const ids=currentTicketIds(state,a);return ids.length>0&&!ids.some(id=>state.tickets?.[id]==="DRAFT"&&deps(id,a,state));}
|
|
166
181
|
if(o==="IMPLEMENT"||o==="REPAIR")return target?state.tickets?.[target]==="REVIEW_REQUIRED":advanced&&state.last_transition?.to==="REVIEW_REQUIRED";
|
|
167
182
|
if(o==="REVIEW")return target?Boolean(state.tickets?.[target]&&state.tickets[target]!=="REVIEW_REQUIRED"):advanced&&state.last_transition?.workflow==="review.record-review";
|
|
168
183
|
if(o==="CONTINUE_PRODUCT")return normalizeStatus(state.product?.status)==="ready";
|
|
@@ -174,8 +189,8 @@ export function intentComplete(i,state,a){
|
|
|
174
189
|
export function selectRoute({state,artifacts:a,evidence=[],reviews=[],repository,intent,routingPolicy}){
|
|
175
190
|
const normal=baseRoute(state,a,evidence,reviews,repository,routingPolicy);if(!intent||intent.desired_outcome==="CONTINUE")return normal;
|
|
176
191
|
if(intentComplete(intent,state,a))return{kind:"INTENT_COMPLETE",reason:intent.completion_kind};
|
|
177
|
-
if(intent.requested_workflow&&legalNow(intent.requested_workflow,state,a)){const map={"implementation.implement-ticket":"READY","implementation.repair-ticket":"REPAIR_REQUIRED","review.review-ticket":"REVIEW_REQUIRED"},st=map[intent.requested_workflow]
|
|
178
|
-
if(["REVIEW","REPAIR"].includes(intent.desired_outcome))return{kind:"
|
|
192
|
+
if(intent.requested_workflow&&legalNow(intent.requested_workflow,state,a)){const map={"implementation.implement-ticket":"READY","implementation.repair-ticket":"REPAIR_REQUIRED","review.review-ticket":"REVIEW_REQUIRED"},st=map[intent.requested_workflow];let ticket=st&&Object.keys(state.tickets??{}).sort().find(id=>state.tickets[id]===st);if(intent.requested_workflow==="planning.create-tickets")ticket=currentTicketIds(state,a).find(id=>state.tickets?.[id]==="DRAFT"&&deps(id,a,state))??null;return{kind:"DISPATCH_READY",workflow:intent.requested_workflow,...(ticket?{ticket}:{})};}
|
|
193
|
+
if(["REVIEW","REPAIR"].includes(intent.desired_outcome))return{kind:"BLOCKED",terminal:"BLOCKED",reason:intent.desired_outcome+"_PRECONDITION_UNSATISFIED"};
|
|
179
194
|
return normal;
|
|
180
195
|
}
|
|
181
196
|
export function applyReconciliation(state,r,repo){
|
|
@@ -3,22 +3,21 @@ import {spawnSync} from "node:child_process";
|
|
|
3
3
|
import {mkdir,readFile,readdir,rm,writeFile} from "node:fs/promises";
|
|
4
4
|
import {basename,join,resolve} from "node:path";
|
|
5
5
|
import {applyReconciliation,handoffBasis,planNext} from "./orchestration-engine.mjs";
|
|
6
|
+
import {parseFrontmatter} from "./frontmatter.mjs";
|
|
6
7
|
|
|
7
8
|
const argv=process.argv.slice(2);let workspaceArg=".",mode="prepare",invokeSkill=null;
|
|
8
9
|
for(let i=0;i<argv.length;i++){if(argv[i]==="--workspace"&&argv[i+1])workspaceArg=argv[++i];else if(argv[i]==="--inspect-only")mode="inspect";else if(argv[i]==="--check-handoff")mode="check";else if(argv[i]==="--reconcile-one")mode="reconcile";else if(argv[i]==="--invoke-skill"&&argv[i+1])invokeSkill=argv[++i];else{process.stderr.write("usage: orchestration-runtime.mjs [--workspace <path>] [--invoke-skill <id>] [--inspect-only|--check-handoff|--reconcile-one]\n");process.exit(2);}}
|
|
9
10
|
const workspace=resolve(workspaceArg),systemRoot=join(workspace,".yaaw-core","system"),projectRoot=join(workspace,".yaaw-core","project"),runtimeRoot=join(workspace,".yaaw-core","runtime");
|
|
10
11
|
const observedPath=join(runtimeRoot,"observed-state.json"),handoffPath=join(runtimeRoot,"handoff.json"),intentPath=join(runtimeRoot,"intent.json"),statePath=join(projectRoot,"state.json");
|
|
11
12
|
const json=async p=>JSON.parse(await readFile(p,"utf8"));const maybe=async p=>{try{return await json(p)}catch{return null}};const uniq=a=>[...new Set(a.filter(Boolean))];
|
|
12
|
-
function
|
|
13
|
-
async function fm(path){try{const l=(await readFile(path,"utf8")).split(/\r?\n/);if(l[0]?.trim()!=="---")return null;const n=l.slice(1).findIndex(x=>x.trim()==="---");if(n<0)return null;const d={};for(const x of l.slice(1,n+1)){if(!x.trim()||/^\s/.test(x)||!x.includes(":"))continue;const i=x.indexOf(":");d[x.slice(0,i).trim()]=scalar(x.slice(i+1));}return d}catch{return null}}
|
|
14
|
-
async function review(path){try{const l=(await readFile(path,"utf8")).split(/\r?\n/);const n=l.slice(1).findIndex(x=>x.trim()==="---");if(l[0]?.trim()!=="---"||n<0)return null;const d={repository:{}};let repo=false;for(const x of l.slice(1,n+1)){if(/^repository:\s*$/.test(x)){repo=true;continue}const m=x.match(/^\s{2}([A-Za-z0-9_]+):\s*(.*)$/);if(repo&&m){d.repository[m[1]]=scalar(m[2]);continue}if(!/^\S/.test(x))continue;repo=false;const t=x.match(/^([A-Za-z0-9_]+):\s*(.*)$/);if(t)d[t[1]]=scalar(t[2]);}return d}catch{return null}}
|
|
13
|
+
async function fm(path){try{return parseFrontmatter(await readFile(path,"utf8"))}catch{return null}}
|
|
15
14
|
async function list(dir,re){try{return(await readdir(dir,{withFileTypes:true})).filter(x=>x.isFile()&&re.test(x.name)).map(x=>join(dir,x.name)).sort()}catch{return[]}}
|
|
16
15
|
function run(tool,args){const r=spawnSync(process.execPath,[tool,...args],{cwd:workspace,encoding:"utf8",windowsHide:true,maxBuffer:64*1024*1024});try{return JSON.parse(r.stdout||"{}")}catch{throw new Error((r.stderr||"").trim()||"invalid JSON from "+basename(tool))}}
|
|
17
16
|
function framework(raw,status=raw?.status??"UNKNOWN"){return{yaaw_version:String(raw?.package_version??"unknown"),system_schema:Number(raw?.system_schema??1),installation_schema:Number(raw?.installation_schema??1),project_schema:Number(raw?.project_schema??1),manifest_digest:String(raw?.manifest_digest??"sha256:unknown"),integrity_status:status,modified:Array.isArray(raw?.modified)?raw.modified:[],missing:Array.isArray(raw?.missing)?raw.missing:[],local_overrides:Array.isArray(raw?.local_overrides)?raw.local_overrides:[],repair_required:status!=="HEALTHY"}}
|
|
18
17
|
function repo(raw){return{schema:"yaaw.repository-identity/v2",algorithm:raw?.algorithm??null,status:raw?.status??"UNAVAILABLE",workspace_scope:String(raw?.workspace_scope??"."),git_root_relation:raw?.git_root_relation??"unknown",head_commit:raw?.head_commit??null,dirty:typeof raw?.dirty==="boolean"?raw.dirty:null,worktree_digest:raw?.worktree_digest??null,components:raw?.components??null,changed_paths:Array.isArray(raw?.changed_paths)?raw.changed_paths:[],error:raw?.error??null}}
|
|
19
18
|
async function contracts(){const names=["workflows","execution-policy","role-io","routing-policy","handoff-policy","artifacts","skills","transitions","reconciliation-policy"];const vals=await Promise.all(names.map(n=>json(join(systemRoot,"registries",n+".json"))));const c=Object.fromEntries(names.map((n,i)=>[n.replaceAll("-","_"),vals[i]])),errors=[];for(const[w,p]of Object.entries(c.handoff_policy.workflows??{})){const reg=c.workflows[w],ex=c.execution_policy.workflows?.[w],io=c.role_io.roles?.[reg?.role];if(!reg)errors.push("unknown workflow "+w);if(!ex)errors.push("no execution policy "+w);if(!io)errors.push("unknown role "+reg?.role);if(io){const br=(p.reads??[]).filter(x=>!io.reads.includes(x)),bw=(p.writes??[]).filter(x=>!io.writes.includes(x));if(br.length)errors.push(w+" bad reads "+br);if(bw.length)errors.push(w+" bad writes "+bw)}}return{...c,errors}}
|
|
20
19
|
const rel=p=>p.replace(workspace+"/","").replaceAll("\\","/"),specId=v=>String(v??"").match(/SPEC-[0-9]+/)?.[0]??null;
|
|
21
|
-
async function inspect(state){const pp=join(projectRoot,"product.md"),ep=join(projectRoot,"engineering.md"),specs={},tickets={},reviews=[],evidence=[];for(const p of await list(join(projectRoot,"specs"),/^SPEC-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)specs[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"tickets"),/^TASK-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)tickets[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"reviews"),/^TASK-[0-9]+-R[0-9]+\.md$/)){const v=await
|
|
20
|
+
async function inspect(state){const pp=join(projectRoot,"product.md"),ep=join(projectRoot,"engineering.md"),specs={},tickets={},reviews=[],evidence=[];for(const p of await list(join(projectRoot,"specs"),/^SPEC-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)specs[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"tickets"),/^TASK-[0-9]+\.md$/)){const m=await fm(p);if(m?.id)tickets[m.id]={path:rel(p),meta:m}}for(const p of await list(join(projectRoot,"reviews"),/^TASK-[0-9]+-R[0-9]+\.md$/)){const v=await fm(p);if(v)reviews.push({path:rel(p),value:v})}for(const p of await list(join(projectRoot,"evidence"),/\.json$/)){try{evidence.push({path:rel(p),value:await json(p)})}catch{}}return{product:{path:rel(pp),meta:await fm(pp)},engineering:{path:rel(ep),meta:await fm(ep)},specs,tickets,reviews,evidence,research:(await list(join(projectRoot,"research"),/^RSH-.*\.md$/)).map(rel),rules:(await list(join(projectRoot,"rules"),/\.md$/)).map(rel),activeSpecId:specId(state?.planning?.active_spec)}}
|
|
22
21
|
function summary(a){return{product:a.product.meta?a.product:null,engineering:a.engineering.meta?a.engineering:null,specs:a.specs,tickets:a.tickets,reviews:a.reviews.map(x=>x.path),evidence:a.evidence.map(x=>x.path)}}
|
|
23
22
|
function requirement(r,repository){return r==="NONE"||r==="INSPECT"&&["READY","UNVERSIONED"].includes(repository.status)||r==="IDENTITY"&&repository.status==="READY"}
|
|
24
23
|
function refs(policy,a,ticket,state){const out=[];for(const x of policy.reads??[]){if(x==="product"&&a.product.meta)out.push(a.product.path);else if(x==="engineering"&&a.engineering.meta)out.push(a.engineering.path);else if(x==="spec"){const id=specId(state?.planning?.active_spec);if(id&&a.specs[id])out.push(a.specs[id].path)}else if(x==="ticket"&&ticket&&a.tickets[ticket])out.push(a.tickets[ticket].path);else if(x==="evidence")out.push(...a.evidence.map(v=>v.path));else if(x==="review")out.push(...a.reviews.map(v=>v.path));else if(x==="engineering_research")out.push(...a.research);else if(x==="project_rule")out.push(...a.rules);else if(x==="state")out.push(".yaaw-core/project/state.json")}return uniq(out)}
|
|
@@ -14,7 +14,7 @@ The runtime reads the machine routing/execution/role-I/O/handoff policies, curre
|
|
|
14
14
|
|
|
15
15
|
- `DISPATCH_READY`: consume the persisted handoff; do not rebuild it.
|
|
16
16
|
- `RECONCILE_REQUIRED`: execute only `orchestration.reconcile-state` before preparing again.
|
|
17
|
-
- `ROOT_ACTION`: execute only the selected Orchestrator recovery workflow.
|
|
17
|
+
- `ROOT_ACTION`: execute only the selected Orchestrator recovery workflow. This result is valid only for a proven interruption boundary; ordinary `DRAFT` admission routes to Planner.
|
|
18
18
|
- `TERMINAL`/`BLOCKED`/`FRAMEWORK_STOP`: write no executable handoff.
|
|
19
19
|
|
|
20
20
|
## Boundary
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
# Recover interruption
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
|
-
Resume safely after context/session failure without duplicating already-completed work.
|
|
4
|
+
Resume safely after an actual context/session failure without duplicating already-completed work.
|
|
5
|
+
|
|
6
|
+
This workflow is interruption-specific. It is not the generic fallback for an unresolved lifecycle state, a `DRAFT` ticket, missing planning admission, or a shortcut precondition failure.
|
|
5
7
|
|
|
6
8
|
## Inputs
|
|
7
9
|
Reconciled state, durable artifacts, implementation-start/verification evidence, reviews, and repository identity.
|
|
8
10
|
|
|
11
|
+
## Selection guard
|
|
12
|
+
Select `orchestration.recover-interruption` only when durable evidence indicates an interrupted execution boundary that cannot yet be classified by the ordinary semantic routes. The normal example is `IN_PROGRESS` with missing implementation-start provenance after a worker/session loss.
|
|
13
|
+
|
|
14
|
+
Do not select recovery merely because no Implementer handoff is currently available.
|
|
15
|
+
|
|
9
16
|
## Procedure
|
|
10
17
|
1. Discard stale runtime caches/handoffs.
|
|
11
18
|
2. Identify the last trustworthy durable boundary.
|
|
12
19
|
3. For `IN_PROGRESS`: current PASS verification is reconciled to review; valid start evidence without PASS verification routes to `implementation.verify-ticket`; missing start evidence requires explicit recovery/blocking proof.
|
|
13
20
|
4. Never rerun full implementation merely because a worker response disappeared.
|
|
14
21
|
5. A source-current review artifact is adopted before another Reviewer dispatch.
|
|
15
|
-
6. Use only legal transitions with provenance; if the boundary cannot be proven, return
|
|
22
|
+
6. Use only legal transitions with provenance; if the interruption boundary cannot be proven, return an exact typed blocker rather than repeatedly selecting recovery.
|
|
23
|
+
7. If inspection proves there is no interrupted execution boundary, return immediately to `orchestration.route`. Do not remain parked in recovery.
|
|
24
|
+
|
|
25
|
+
## Explicit non-cases
|
|
26
|
+
- registered `DRAFT` ticket with current planning/spec sources -> Planner admission;
|
|
27
|
+
- `DRAFT` whose prerequisite ticket is runnable -> continue the prerequisite;
|
|
28
|
+
- all accepted work PASS/CANCELLED with scope OPEN/UNKNOWN -> Planner next frontier;
|
|
29
|
+
- direct Review/Repair shortcut with unsatisfied lifecycle preconditions -> typed shortcut precondition failure.
|
|
16
30
|
|
|
17
31
|
## Output
|
|
18
|
-
A safe next boundary or exact blocker, then control returns to `orchestration.route`.
|
|
32
|
+
A safe next boundary or exact interruption blocker, then control returns to `orchestration.route`.
|
|
@@ -19,8 +19,8 @@ Repeat:
|
|
|
19
19
|
1. run deterministic preparation once;
|
|
20
20
|
2. on `FRAMEWORK_STOP`, stop without lifecycle mutation;
|
|
21
21
|
3. on `RECONCILE_REQUIRED`, apply exactly one reconciliation with `--reconcile-one`, then observe again;
|
|
22
|
-
4. on `ROOT_ACTION`, execute only the returned Orchestrator recovery workflow, then observe again;
|
|
23
|
-
5. on `DISPATCH_READY`, execute exactly the persisted handoff once;
|
|
22
|
+
4. on `ROOT_ACTION`, execute only the returned Orchestrator recovery workflow, then observe again; `ROOT_ACTION` is reserved for evidence-backed interruption recovery, never ordinary `DRAFT` admission;
|
|
23
|
+
5. on `DISPATCH_READY`, execute exactly the persisted handoff once; a dependency-satisfied current `DRAFT` routes to Planner admission, not recovery;
|
|
24
24
|
6. after any worker outcome or disappearance, discard worker text as authority and observe durable reality again;
|
|
25
25
|
7. on `INTENT_COMPLETE`, `TERMINAL`, `BLOCKED`, or human input, stop.
|
|
26
26
|
|
|
@@ -1,23 +1,50 @@
|
|
|
1
1
|
# Create tickets
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
|
-
|
|
4
|
+
Create bounded dependency-aware implementation contracts for one current accepted spec, and re-evaluate already-registered `DRAFT` tickets when their admission prerequisites may have changed.
|
|
5
5
|
|
|
6
6
|
## Preconditions
|
|
7
|
-
Source spec is `ACCEPTED`, product/engineering revisions remain current, and repository requirement `IDENTITY` is satisfied with repository status `READY`.
|
|
7
|
+
Source spec is `ACCEPTED`, product/engineering revisions remain current, planning status is `ready` with readiness `PASS`, and repository requirement `IDENTITY` is satisfied with repository status `READY`.
|
|
8
|
+
|
|
9
|
+
## Existing-ticket rule
|
|
10
|
+
Before allocating any new `TASK-NNN` ID, inspect current tickets for the active spec. A current `DRAFT` is an admission candidate, not evidence that ticket creation must start over.
|
|
11
|
+
|
|
12
|
+
Never duplicate an existing ticket merely because it is still `DRAFT`.
|
|
8
13
|
|
|
9
14
|
## Procedure
|
|
10
15
|
1. If exact repository identity is unavailable, return `PRECONDITION_UNSATISFIED:REPOSITORY_IDENTITY_UNAVAILABLE`; do not admit executable tickets.
|
|
11
|
-
2.
|
|
12
|
-
3.
|
|
13
|
-
4.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
2. Inspect current ticket artifacts and reconciled lifecycle state for the active spec before creating anything.
|
|
17
|
+
3. If routing supplied a current `DRAFT` ticket, re-evaluate that ticket first.
|
|
18
|
+
4. A `DRAFT` may become `READY` only when:
|
|
19
|
+
- its product, engineering, spec, and ticket revisions are current;
|
|
20
|
+
- planning remains `ready` with readiness `PASS`;
|
|
21
|
+
- every declared dependency is `PASS` in reconciled state;
|
|
22
|
+
- repository identity is current and trustworthy;
|
|
23
|
+
- its acceptance contract remains implementable without inventing product or engineering decisions; and
|
|
24
|
+
- any explicit execution prerequisite recorded in its status rationale is currently satisfied.
|
|
25
|
+
5. If the contract itself must change, do not disguise that as admission. Route through `planning.replan`, revise the contract with proper revision/invalidation semantics, then reassess admission.
|
|
26
|
+
6. If the contract is unchanged and admission now passes, update only the ticket artifact status from `DRAFT` to `READY`. Do not rewrite history and do not increment ticket revision solely for the lifecycle admission bit.
|
|
27
|
+
7. If dependencies are not yet `PASS`, keep the ticket `DRAFT`. Normal orchestration should continue the prerequisite ticket rather than repeatedly invoking Planner.
|
|
28
|
+
8. For genuinely missing work, split the accepted spec into coherent `TASK-NNN` units sized for a fresh Implementer.
|
|
29
|
+
9. Apply `.yaaw-core/system/rules/changeability.md` while defining boundaries.
|
|
30
|
+
10. Each ticket metadata records source spec/revision, product revision, engineering decision IDs, dependencies, expertise, ticket revision, and status.
|
|
31
|
+
11. Body records product requirements, relevant areas, required behavior, allowed scope, non-goals, acceptance criteria, required tests, relevant engineering/changeability constraints, and a precise status rationale when left `DRAFT`.
|
|
32
|
+
12. Ensure supporting refactors are admitted only when necessary for safe implementation or verification.
|
|
33
|
+
13. Validate ticket template/metadata before returning success.
|
|
34
|
+
|
|
35
|
+
## Durable admission fact
|
|
36
|
+
Planner owns the semantic decision that a ticket artifact is `READY`. Planner still never writes `state.json`.
|
|
37
|
+
|
|
38
|
+
After Planner persists `DRAFT -> READY` in the current ticket artifact, Orchestrator must observe that fact, validate the legal transition and current sources/dependencies/repository identity, then adopt exactly one `TICKET_ADMISSION` reconciliation into state.
|
|
39
|
+
|
|
40
|
+
Ticket frontmatter therefore does not directly override state. It proposes a Planner-owned durable fact that Orchestrator may adopt only when the admission contract is still valid.
|
|
18
41
|
|
|
19
42
|
## Output
|
|
20
|
-
Dependency-aware tickets requiring no planning-chat memory
|
|
43
|
+
Dependency-aware tickets requiring no planning-chat memory, plus one of:
|
|
44
|
+
- newly-created current ticket artifacts;
|
|
45
|
+
- an existing current `DRAFT` promoted to `READY`;
|
|
46
|
+
- an unchanged `DRAFT` with an exact prerequisite reason; or
|
|
47
|
+
- a typed prerequisite/blocker/replan result.
|
|
21
48
|
|
|
22
49
|
## Registration
|
|
23
|
-
Planner writes ticket artifacts only. Orchestrator registers current tickets into `state.json` one reconciliation at a time, preventing duplicate ticket creation
|
|
50
|
+
Planner writes ticket artifacts only. Orchestrator registers new current tickets and adopts later Planner admission facts into `state.json` one reconciliation at a time, preventing duplicate ticket creation and preventing lifecycle jumps.
|
|
@@ -18,8 +18,9 @@ Do not open candidate planning workflow bodies before one route is selected.
|
|
|
18
18
|
6. unresolved human-owned engineering decisions -> `planning.question-round`;
|
|
19
19
|
7. readiness not current -> `planning.readiness-review`;
|
|
20
20
|
8. readiness PASS but no current accepted spec -> `planning.create-spec`;
|
|
21
|
-
9. accepted spec lacks
|
|
22
|
-
10.
|
|
21
|
+
9. accepted spec lacks tickets, or a current dependency-satisfied `DRAFT` needs admission re-evaluation -> `planning.create-tickets`;
|
|
22
|
+
10. current `DRAFT` tickets whose dependencies are not yet `PASS` do not preempt runnable implementation/review work; normal orchestration continues their prerequisites;
|
|
23
|
+
11. otherwise return planning frontier `READY`.
|
|
23
24
|
|
|
24
25
|
After each selected workflow persists durable output, return to routing before loading another workflow.
|
|
25
26
|
|