yaaw-se 0.3.12 → 0.3.13
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 +16 -11
- package/dist/payload/payload.json +1 -1
- package/dist/payload/yaaw-core/system/SYSTEM.md +4 -0
- package/dist/payload/yaaw-core/system/engine/contracts.mjs +147 -0
- package/dist/payload/yaaw-core/system/engine/routing.mjs +19 -15
- package/dist/payload/yaaw-core/system/engine/runtime.mjs +13 -4
- package/dist/payload/yaaw-core/system/kernel.yaml +3 -0
- package/dist/payload/yaaw-core/system/roles/prd.md +3 -3
- package/package.json +5 -2
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "payload.json",
|
|
31
|
-
"sha256": "
|
|
31
|
+
"sha256": "11921054038944aad1e18d9818ca049d06157201ea0bf9684e59b29a0a25096f",
|
|
32
32
|
"bytes": 57
|
|
33
33
|
},
|
|
34
34
|
{
|
|
@@ -96,6 +96,11 @@
|
|
|
96
96
|
"sha256": "6189616f6ddfb5992dcc22e562f2f2c061ce5f3202c09dfce0f5c9adef5bf42f",
|
|
97
97
|
"bytes": 1247
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
"path": "yaaw-core/system/engine/contracts.mjs",
|
|
101
|
+
"sha256": "b9541c15dcbb842da1812876c862e51b9a4422fcead81b274c19a864bf0885b3",
|
|
102
|
+
"bytes": 10180
|
|
103
|
+
},
|
|
99
104
|
{
|
|
100
105
|
"path": "yaaw-core/system/engine/frontmatter.mjs",
|
|
101
106
|
"sha256": "2c90097563a91c52e6f9c7748dac583e67967fd4a9748e393bf61d753594277e",
|
|
@@ -113,18 +118,18 @@
|
|
|
113
118
|
},
|
|
114
119
|
{
|
|
115
120
|
"path": "yaaw-core/system/engine/routing.mjs",
|
|
116
|
-
"sha256": "
|
|
117
|
-
"bytes":
|
|
121
|
+
"sha256": "7e051c636325d1c94b1a31ba409406e6ce9b5918857a3d39bf5fbd3b14c36365",
|
|
122
|
+
"bytes": 13728
|
|
118
123
|
},
|
|
119
124
|
{
|
|
120
125
|
"path": "yaaw-core/system/engine/runtime.mjs",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"bytes":
|
|
126
|
+
"sha256": "605bef2be22d18cbeaa04cf0b2bbdec13a23ab5c3db857706e29bd309c25df63",
|
|
127
|
+
"bytes": 14049
|
|
123
128
|
},
|
|
124
129
|
{
|
|
125
130
|
"path": "yaaw-core/system/kernel.yaml",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"bytes":
|
|
131
|
+
"sha256": "706475307535f2e1c8042aab932e78cbada4af36460391d4fa29ad5067fb012c",
|
|
132
|
+
"bytes": 22539
|
|
128
133
|
},
|
|
129
134
|
{
|
|
130
135
|
"path": "yaaw-core/system/modules/changeability.md",
|
|
@@ -168,8 +173,8 @@
|
|
|
168
173
|
},
|
|
169
174
|
{
|
|
170
175
|
"path": "yaaw-core/system/roles/prd.md",
|
|
171
|
-
"sha256": "
|
|
172
|
-
"bytes":
|
|
176
|
+
"sha256": "9792407afb6ce55275b0acedf9727d7d79fbb4cfbd72aa5641078718c32e125a",
|
|
177
|
+
"bytes": 3874
|
|
173
178
|
},
|
|
174
179
|
{
|
|
175
180
|
"path": "yaaw-core/system/roles/reviewer.md",
|
|
@@ -283,8 +288,8 @@
|
|
|
283
288
|
},
|
|
284
289
|
{
|
|
285
290
|
"path": "yaaw-core/system/SYSTEM.md",
|
|
286
|
-
"sha256": "
|
|
287
|
-
"bytes":
|
|
291
|
+
"sha256": "47d318e533ded5347fadaf63089f631b167cf7a28fc64377aa7d21f34c3f3a5e",
|
|
292
|
+
"bytes": 10940
|
|
288
293
|
},
|
|
289
294
|
{
|
|
290
295
|
"path": "yaaw-core/system/templates/dispatch-failures.json",
|
|
@@ -82,6 +82,10 @@ Workers never privately command or spawn peer YAAW authorities. After every work
|
|
|
82
82
|
|
|
83
83
|
Before semantic routing, verify the installer manifest and package-managed system bytes. `MODIFIED`, `MISSING`, `LOCAL_OVERRIDE`, unknown integrity, or contract inconsistency fails closed. Semantic workers never edit `.yaaw-core/system/**` to unblock themselves.
|
|
84
84
|
|
|
85
|
+
Dispatch completeness is a framework invariant. A worker dispatch target is the union of non-Orchestrator public-skill requests, deterministic router targets (the values used by router branches), and declared worker handoffs. Other role operations are internal suboperations; root Orchestrator operations do not require worker handoffs. Every target must resolve a workflow, owning role, valid handoff, role-bounded reads/writes, active artifact, authorized existing modules, and the typed worker result protocol. Public skill metadata and completion vocabulary must agree with the kernel. `engine/contracts.mjs` is the canonical validator used by core, distribution, payload generation/verification, and runtime. Missing contracts stop as framework defects, never consumer semantic blockers.
|
|
86
|
+
|
|
87
|
+
Product revision changes the downstream product basis. Orchestrator derives stale ticket authority and performs only legal status reconciliation; Planner owns changed downstream contracts. Engineering readiness based on an older product revision cannot authorize a specification. Clarity-only product refinement preserves semantic revision and downstream source authority.
|
|
88
|
+
|
|
85
89
|
## Repository identity
|
|
86
90
|
|
|
87
91
|
Repository identity is produced only by `.yaaw-core/system/engine/repository-identity.mjs`; roles do not reimplement it. Runtime caches, ticket lifecycle files, immutable implementation evidence, and immutable reviews are excluded from the application worktree digest because their normal lifecycle writes would otherwise invalidate their own basis. Ticket semantic freshness is instead bound explicitly by ticket revision and ticket content digest in handoffs/evidence/reviews.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { readFile, lstat, realpath } from 'node:fs/promises';
|
|
2
|
+
import { resolve, relative, isAbsolute, join } from 'node:path';
|
|
3
|
+
// The router uses these values at each branch; there is no separate target inventory.
|
|
4
|
+
export const ROUTES = Object.freeze({product:'prd.route',planning:'planning.route',spec:'planning.create-spec',tickets:'planning.create-tickets',replan:'planning.replan',repair:'implementation.repair-ticket',review:'review.review-ticket',verify:'implementation.verify-ticket',implement:'implementation.implement-ticket'});
|
|
5
|
+
export const ROUTER_DISPATCH_TARGETS = Object.freeze([...new Set(Object.values(ROUTES))]);
|
|
6
|
+
// Typed worker outcomes consumed by the host continuation/lifecycle protocol.
|
|
7
|
+
const RESULTS = {
|
|
8
|
+
'prd.route':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
9
|
+
'prd.revise':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
10
|
+
'prd.refine':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
11
|
+
'planning.route':['READY','HUMAN_INPUT_REQUIRED','REPLAN','BLOCKED'],
|
|
12
|
+
'planning.readiness-review':['PASS','MISSING_DECISIONS','PRODUCT_GAP','REPLAN','BLOCKED'],
|
|
13
|
+
'planning.create-spec':['SUCCESS','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
14
|
+
'planning.create-tickets':['SUCCESS','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
15
|
+
'planning.replan':['READY','HUMAN_INPUT_REQUIRED','BLOCKED'],
|
|
16
|
+
'implementation.implement-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','REPLAN_REQUIRED','PRECONDITION_UNSATISFIED','BLOCKED'],
|
|
17
|
+
'implementation.verify-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','IMPLEMENTATION_INCOMPLETE','REPLAN_REQUIRED','BLOCKED'],
|
|
18
|
+
'implementation.repair-ticket':['VERIFICATION_PASS','VERIFICATION_FAIL','REPLAN_REQUIRED','BLOCKED'],
|
|
19
|
+
'review.review-ticket':['PASS','REPAIR','REPLAN','BLOCKED']
|
|
20
|
+
};
|
|
21
|
+
// A shortcut's completion must describe the operation it actually requests.
|
|
22
|
+
const INTENTS = {
|
|
23
|
+
CONTINUE:['orchestration.route','CONTINUE_UNTIL_STOP'],
|
|
24
|
+
CONTINUE_PRODUCT:['prd.route','PRODUCT_READY'],
|
|
25
|
+
REVISE_PRODUCT:['prd.revise','PRODUCT_REVISION_ADVANCED'],
|
|
26
|
+
REFINE_PRODUCT:['prd.refine','PRODUCT_REFINED'],
|
|
27
|
+
CONTINUE_PLANNING:['planning.route','PLANNING_READY'],
|
|
28
|
+
PLANNING_REVIEW:['planning.readiness-review','PLANNING_READINESS_DECIDED'],
|
|
29
|
+
CREATE_SPEC:['planning.create-spec','CURRENT_SPEC_ACCEPTED'],
|
|
30
|
+
CREATE_TICKETS:['planning.create-tickets','CURRENT_TICKETS_REGISTERED'],
|
|
31
|
+
IMPLEMENT:['implementation.implement-ticket','TICKET_REVIEW_REQUIRED'],
|
|
32
|
+
REPAIR:['implementation.repair-ticket','TICKET_REVIEW_REQUIRED'],
|
|
33
|
+
REVIEW:['review.review-ticket','TICKET_REVIEW_APPLIED']
|
|
34
|
+
};
|
|
35
|
+
export function dispatchableWorkflows(kernel) {
|
|
36
|
+
return new Set([...ROUTER_DISPATCH_TARGETS,...Object.values(kernel.skills??{}).filter(s=>s.role!=='orchestrator').map(s=>s.requested_workflow),...Object.keys(kernel.handoffs??{})]);
|
|
37
|
+
}
|
|
38
|
+
const managedPath=p=>typeof p==='string'&&p.startsWith('.yaaw-core/system/')&&!p.includes('\\')&&!p.split('/').includes('..')&&p.endsWith('.md');
|
|
39
|
+
async function managedFile(system,path) {
|
|
40
|
+
if(!managedPath(path))throw new Error(`invalid managed system reference ${path}`);
|
|
41
|
+
const target=resolve(system,path.slice('.yaaw-core/system/'.length)),root=await realpath(system),actual=await realpath(target),rel=relative(root,actual);
|
|
42
|
+
if(rel.startsWith('..')||isAbsolute(rel)||!(await lstat(target)).isFile())throw new Error(`invalid managed system file ${path}`);
|
|
43
|
+
return target;
|
|
44
|
+
}
|
|
45
|
+
const operationHeader=(id,w)=>`## Operation: ${w.operation} (\`${id}\`)`;
|
|
46
|
+
export async function validateDispatchFiles(system,k,workflow,expertise=[]) {
|
|
47
|
+
const paths=[k.roles[k.workflows[workflow]?.role]?.document,...expertise],errors=[];
|
|
48
|
+
for(const path of paths)try{await managedFile(system,path)}catch{errors.push(`${workflow}: missing/invalid dispatch file ${path}`)}
|
|
49
|
+
const w=k.workflows[workflow];
|
|
50
|
+
try{const text=await readFile(await managedFile(system,k.roles[w.role].document),"utf8");if(!text.includes(operationHeader(workflow,w)))errors.push(`${workflow}: missing selected role operation`)}catch{}
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
const stringList=x=>Array.isArray(x)&&x.every(v=>typeof v==='string'&&v.length>0)&&new Set(x).size===x.length;
|
|
54
|
+
const sameSet=(a,b)=>stringList(a)&&stringList(b)&&a.length===b.length&&a.every(x=>b.includes(x));
|
|
55
|
+
export function validateContracts(k) {
|
|
56
|
+
const errors=[],roles=k.roles??{},workflows=k.workflows??{},artifacts=k.artifacts??{},modules=k.modules??{},handoffs=k.handoffs??{};
|
|
57
|
+
const error=(id,message)=>errors.push(`${id}: ${message}`);
|
|
58
|
+
for(const [id,a] of Object.entries(artifacts)) {
|
|
59
|
+
if(!roles[a.owner]&&!['lifecycle','installer','none'].includes(a.owner))error(id,'unknown artifact owner');
|
|
60
|
+
if(a.embedded_in&&!artifacts[a.embedded_in])error(id,'unknown embedded artifact');
|
|
61
|
+
if(!a.path&&!a.path_pattern&&!a.embedded_in&&!a.virtual)error(id,'artifact has no locator');
|
|
62
|
+
}
|
|
63
|
+
for(const [id,r] of Object.entries(roles)){
|
|
64
|
+
if(!managedPath(r.document))error(id,'invalid role document reference');
|
|
65
|
+
for(const kind of ['reads','writes']) {
|
|
66
|
+
if(!stringList(r[kind]))error(id,`invalid role ${kind}`);
|
|
67
|
+
else for(const a of r[kind])if(!artifacts[a])error(id,`unknown ${kind} artifact ${a}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for(const [id,w] of Object.entries(workflows)) {
|
|
71
|
+
if(!roles[w.role])error(id,`unknown owning role ${w.role}`);
|
|
72
|
+
if(typeof w.operation!=='string'||!w.operation)error(id,'missing operation');
|
|
73
|
+
if(!['NONE','INSPECT','IDENTITY'].includes(w.repository_requirement))error(id,'invalid repository requirement');
|
|
74
|
+
}
|
|
75
|
+
for(const [id,s] of Object.entries(k.skills??{})) {
|
|
76
|
+
const w=workflows[s.requested_workflow];
|
|
77
|
+
if(!w)error(id,`unknown requested workflow ${s.requested_workflow}`);
|
|
78
|
+
else if(w.role!==s.role)error(id,'skill role differs from workflow owner');
|
|
79
|
+
if(!roles[s.role])error(id,'unknown skill role');
|
|
80
|
+
const intent=INTENTS[s.desired_outcome];
|
|
81
|
+
if(!intent||s.requested_workflow!==intent[0]||s.completion_kind!==intent[1])error(id,'shortcut intent/workflow/completion semantics disagree');
|
|
82
|
+
}
|
|
83
|
+
for(const id of dispatchableWorkflows(k)) {
|
|
84
|
+
const w=workflows[id];
|
|
85
|
+
if(!w)error(id,'dispatch target has no workflow');
|
|
86
|
+
if(w?.role==='orchestrator')error(id,'root operation cannot be a worker dispatch target');
|
|
87
|
+
if(!handoffs[id])error(id,'dispatch target has no handoff contract');
|
|
88
|
+
}
|
|
89
|
+
for(const [id,h] of Object.entries(handoffs)) {
|
|
90
|
+
const w=workflows[id],r=roles[w?.role];
|
|
91
|
+
if(!w||!r){error(id,'handoff has no workflow/owning role');continue}
|
|
92
|
+
for(const kind of ['reads','writes']) {
|
|
93
|
+
if(!stringList(h[kind])){error(id,`invalid handoff ${kind}`);continue}
|
|
94
|
+
for(const a of h[kind]) {
|
|
95
|
+
if(!artifacts[a])error(id,`unknown ${kind} artifact ${a}`);
|
|
96
|
+
if(!r[kind]?.includes(a))error(id,`${kind} outside ${w.role} authority: ${a}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if(!artifacts[h.active_artifact])error(id,`unknown active artifact ${h.active_artifact}`);
|
|
100
|
+
else if(!h.reads?.includes(h.active_artifact)&&!h.writes?.includes(h.active_artifact))error(id,'active artifact outside handoff I/O');
|
|
101
|
+
if(!stringList(h.required_modules))error(id,'invalid required modules');
|
|
102
|
+
else for(const m of h.required_modules)if(!modules[m]?.usable_by?.includes(w.role))error(id,`missing/unauthorized module ${m}`);
|
|
103
|
+
if(typeof h.ticket_modules!=='boolean')error(id,'invalid ticket_modules');
|
|
104
|
+
if(typeof h.expected_output!=='string'||!h.expected_output.trim())error(id,'missing expected output');
|
|
105
|
+
if(!sameSet(h.result_vocabulary,RESULTS[id]))error(id,'result vocabulary incompatible with worker protocol');
|
|
106
|
+
}
|
|
107
|
+
for(const [id,m] of Object.entries(modules)) {
|
|
108
|
+
if(!managedPath(m.path))error(id,'invalid module path reference');
|
|
109
|
+
if(!stringList(m.usable_by)||m.usable_by.some(r=>!roles[r]))error(id,'unknown module role');
|
|
110
|
+
}
|
|
111
|
+
for(const row of k.lifecycle?.legal??[]) {
|
|
112
|
+
if(!workflows[row.workflow]||!roles[row.owner])error('lifecycle','unresolved workflow/owner');
|
|
113
|
+
if(row.writer&&!roles[row.writer])error('lifecycle','unknown writer');
|
|
114
|
+
}
|
|
115
|
+
const routing=k.lifecycle?.routing??{};
|
|
116
|
+
for(const [name,value] of Object.entries(routing)) {
|
|
117
|
+
if(name.endsWith('_workflow')&&!ROUTER_DISPATCH_TARGETS.includes(value))error(name,'routing workflow is not a router target');
|
|
118
|
+
}
|
|
119
|
+
for(const row of Object.values(routing.invalidation_routes??{}))if(!ROUTER_DISPATCH_TARGETS.includes(row.workflow))error('invalidation_routes','unknown router target');
|
|
120
|
+
return errors;
|
|
121
|
+
}
|
|
122
|
+
export async function validateSystemContracts(system,skills=null) {
|
|
123
|
+
const k=JSON.parse(await readFile(join(system,'kernel.yaml'),'utf8')),errors=validateContracts(k);
|
|
124
|
+
async function textAt(path,id) {
|
|
125
|
+
try{return await readFile(await managedFile(system,path),'utf8')}catch{errors.push(`${id}: missing/invalid referenced file ${path}`);return ''}
|
|
126
|
+
}
|
|
127
|
+
for(const [id,r] of Object.entries(k.roles??{})) {
|
|
128
|
+
const text=await textAt(r.document,id);
|
|
129
|
+
for(const [wid,w] of Object.entries(k.workflows??{}))if(w.role===id&&!text.includes(operationHeader(wid,w)))errors.push(`${wid}: missing role operation`);
|
|
130
|
+
}
|
|
131
|
+
for(const [id,m] of Object.entries(k.modules??{}))await textAt(m.path,id);
|
|
132
|
+
if(skills) {
|
|
133
|
+
const {readdir}=await import('node:fs/promises');
|
|
134
|
+
const dirs=(await readdir(skills,{withFileTypes:true})).filter(e=>e.isDirectory()).map(e=>e.name);
|
|
135
|
+
if(!sameSet(dirs,Object.keys(k.skills??{})))errors.push('public skill directory/catalog mismatch');
|
|
136
|
+
for(const [id,s] of Object.entries(k.skills??{})) {
|
|
137
|
+
try {
|
|
138
|
+
const text=await readFile(join(skills,id,'SKILL.md'),'utf8');
|
|
139
|
+
for(const marker of [`name: ${id}`,`ROLE: \`${s.role}\``,`WORKFLOW: \`${s.requested_workflow}\``,`INTENT: \`${s.desired_outcome}\``, 'system/engine/runtime.mjs',`--invoke-skill ${id}`])if(!text.includes(marker))errors.push(`${id}: skill metadata/runtime invocation mismatch (${marker})`);
|
|
140
|
+
}catch{errors.push(`${id}: missing public skill`)}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Completion and intent enums are already machine contracts; do not duplicate them.
|
|
144
|
+
const schema=JSON.parse(await readFile(join(system,'schemas/intent.schema.json'),'utf8'));
|
|
145
|
+
for(const [id,s] of Object.entries(k.skills??{}))for(const field of ['desired_outcome','completion_kind'])if(!schema.properties[field].enum.includes(s[field]))errors.push(`${id}: invalid ${field}`);
|
|
146
|
+
return errors;
|
|
147
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ROUTES, dispatchableWorkflows } from "./contracts.mjs";
|
|
1
2
|
const ACTIVE = new Set(["READY","IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","PASS"]);
|
|
2
3
|
const TERMINAL = new Set(["PASS","CANCELLED"]);
|
|
3
4
|
const REVIEW_RESULT = { PASS:"PASS", REPAIR:"REPAIR_REQUIRED", REPLAN:"REPLAN_REQUIRED", BLOCKED:"BLOCKED" };
|
|
@@ -28,6 +29,7 @@ export function deriveState(a){
|
|
|
28
29
|
const p=a.product?.meta??{},e=a.engineering?.meta??{};
|
|
29
30
|
const product={artifact:a.product?.path??null,status:String(p.status??"missing"),revision:Number(p.revision??0)};
|
|
30
31
|
const planning={artifact:a.engineering?.path??null,status:String(e.status??"missing"),revision:Number(e.revision??0),current_frontier:e.current_frontier??null,readiness:e.readiness??"pending",scope_status:e.scope_status??"UNKNOWN",active_spec:null};
|
|
32
|
+
if(e.product_revision!==undefined&&Number(e.product_revision)!==product.revision)planning.readiness="pending";
|
|
31
33
|
const accepted=Object.entries(a.specs??{}).filter(([,x])=>x.meta?.status==="ACCEPTED"&&Number(x.meta.product_revision??0)===product.revision&&Number(x.meta.engineering_revision??0)===planning.revision).sort(([x],[y])=>byId(x,y));
|
|
32
34
|
if(accepted.length===1)planning.active_spec=accepted[0][1].path;
|
|
33
35
|
const tickets=Object.fromEntries(Object.entries(a.tickets??{}).map(([id,x])=>[id,String(x.meta?.status??"DRAFT")]));
|
|
@@ -86,7 +88,7 @@ export function intentComplete(intent,state,a){
|
|
|
86
88
|
if(!intent)return false;const o=intent.desired_outcome,target=requestedTicket(intent,state),base=intent.created_from??{};
|
|
87
89
|
if(o==="CONTINUE")return false;
|
|
88
90
|
if(o==="CONTINUE_PRODUCT")return state.product.status==="ready";
|
|
89
|
-
if(o==="REVISE_PRODUCT")return Number(state.product.revision)>Number(base.product_revision??0);
|
|
91
|
+
if(o==="REVISE_PRODUCT")return state.product.status==="ready"&&Number(state.product.revision)>Number(base.product_revision??0);
|
|
90
92
|
if(o==="REFINE_PRODUCT")return Boolean(a.product?.digest&&base.product_digest&&a.product.digest!==base.product_digest);
|
|
91
93
|
if(o==="CONTINUE_PLANNING")return planningReady(state);
|
|
92
94
|
if(o==="PLANNING_REVIEW")return state.planning.readiness!==base.planning_readiness||a.engineering?.digest!==base.engineering_digest;
|
|
@@ -98,23 +100,24 @@ export function intentComplete(intent,state,a){
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
function baseRoute(state,a){
|
|
101
|
-
if(state.product.status!=="ready")return {workflow:
|
|
102
|
-
const replan=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPLAN_REQUIRED");if(replan)return{workflow:
|
|
103
|
-
if(state.planning.status!=="ready"||state.planning.readiness!=="PASS")return{workflow:
|
|
104
|
-
if(!currentSpecId(state))return{workflow:
|
|
103
|
+
if(state.product.status!=="ready")return {workflow:ROUTES.product,ticket:null};
|
|
104
|
+
const replan=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPLAN_REQUIRED");if(replan)return{workflow:ROUTES.replan,ticket:replan};
|
|
105
|
+
if(state.planning.status!=="ready"||state.planning.readiness!=="PASS")return{workflow:ROUTES.planning,ticket:null};
|
|
106
|
+
if(!currentSpecId(state))return{workflow:ROUTES.spec,ticket:null};
|
|
105
107
|
const ids=currentTicketIds(state,a);
|
|
106
|
-
const repair=ids.find(id=>state.tickets[id]==="REPAIR_REQUIRED");if(repair)return{workflow:
|
|
107
|
-
const review=ids.find(id=>state.tickets[id]==="REVIEW_REQUIRED");if(review)return{workflow:
|
|
108
|
-
const progress=ids.find(id=>state.tickets[id]==="IN_PROGRESS");if(progress)return{workflow:
|
|
109
|
-
const ready=ids.find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a));if(ready)return{workflow:
|
|
110
|
-
const draft=ids.find(id=>state.tickets[id]==="DRAFT"&&dependenciesPass(id,state,a));if(draft)return{workflow:
|
|
111
|
-
if(ids.length===0)return{workflow:
|
|
112
|
-
if(ids.every(id=>TERMINAL.has(state.tickets[id]))){if(state.planning.scope_status==="COMPLETE")return{terminal:"COMPLETE"};return{workflow:
|
|
108
|
+
const repair=ids.find(id=>state.tickets[id]==="REPAIR_REQUIRED");if(repair)return{workflow:ROUTES.repair,ticket:repair};
|
|
109
|
+
const review=ids.find(id=>state.tickets[id]==="REVIEW_REQUIRED");if(review)return{workflow:ROUTES.review,ticket:review};
|
|
110
|
+
const progress=ids.find(id=>state.tickets[id]==="IN_PROGRESS");if(progress)return{workflow:ROUTES.verify,ticket:progress};
|
|
111
|
+
const ready=ids.find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a));if(ready)return{workflow:ROUTES.implement,ticket:ready};
|
|
112
|
+
const draft=ids.find(id=>state.tickets[id]==="DRAFT"&&dependenciesPass(id,state,a));if(draft)return{workflow:ROUTES.tickets,ticket:draft};
|
|
113
|
+
if(ids.length===0)return{workflow:ROUTES.tickets,ticket:null};
|
|
114
|
+
if(ids.every(id=>TERMINAL.has(state.tickets[id]))){if(state.planning.scope_status==="COMPLETE")return{terminal:"COMPLETE"};return{workflow:ROUTES.planning,ticket:null};}
|
|
113
115
|
const blocked=ids.find(id=>state.tickets[id]==="BLOCKED");if(blocked)return{blocked:`ticket ${blocked} is BLOCKED`,ticket:blocked};
|
|
114
116
|
return{blocked:"no legal next workflow from current durable artifacts",ticket:null};
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
function legalRequested(workflow,ticket,state,a){
|
|
119
|
+
function legalRequested(kernel,workflow,ticket,state,a){
|
|
120
|
+
if(!dispatchableWorkflows(kernel).has(workflow))return false;
|
|
118
121
|
if(workflow==="prd.route"||workflow==="prd.revise"||workflow==="prd.refine")return true;
|
|
119
122
|
if(workflow==="planning.route"||workflow==="planning.readiness-review")return state.product.status==="ready";
|
|
120
123
|
if(workflow==="planning.create-spec")return planningReady(state)&&!currentSpecId(state);
|
|
@@ -128,13 +131,14 @@ function legalRequested(workflow,ticket,state,a){
|
|
|
128
131
|
export function planNext(kernel,state,a,repo,intent){
|
|
129
132
|
const reconciliation=selectReconciliation(kernel,state,a,repo);if(reconciliation)return{type:"RECONCILE_REQUIRED",reconciliation};
|
|
130
133
|
if(intentComplete(intent,state,a))return{type:"INTENT_COMPLETE",completion_kind:intent.completion_kind};
|
|
131
|
-
if(state.phase==="complete")return{type:"TERMINAL",terminal:"COMPLETE"};
|
|
134
|
+
if(state.phase==="complete"&&!["prd.revise","prd.refine"].includes(intent?.requested_workflow))return{type:"TERMINAL",terminal:"COMPLETE"};
|
|
135
|
+
if(intent?.desired_outcome==="REVISE_PRODUCT"&&Number(state.product.revision)>Number(intent.created_from?.product_revision??0)&&state.product.status!=="ready")return{type:"ROUTE",workflow:ROUTES.product,ticket:null};
|
|
132
136
|
if(intent&&intent.requested_workflow&&intent.requested_workflow!=="orchestration.route"){
|
|
133
137
|
let ticket=requestedTicket(intent,state);
|
|
134
138
|
if(!ticket&&intent.desired_outcome==="IMPLEMENT")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="READY"&&dependenciesPass(id,state,a))??null;
|
|
135
139
|
if(!ticket&&intent.desired_outcome==="REPAIR")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REPAIR_REQUIRED")??null;
|
|
136
140
|
if(!ticket&&intent.desired_outcome==="REVIEW")ticket=Object.keys(state.tickets).sort(byId).find(id=>state.tickets[id]==="REVIEW_REQUIRED")??null;
|
|
137
|
-
if(legalRequested(intent.requested_workflow,ticket,state,a))return{type:"ROUTE",workflow:intent.requested_workflow,ticket};
|
|
141
|
+
if(legalRequested(kernel,intent.requested_workflow,ticket,state,a))return{type:"ROUTE",workflow:intent.requested_workflow,ticket};
|
|
138
142
|
return{type:"BLOCKED",reason:`PRECONDITION_UNSATISFIED:${intent.requested_workflow}`,ticket};
|
|
139
143
|
}
|
|
140
144
|
return{type:"ROUTE",...baseRoute(state,a)};
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join, relative, resolve } from "node:path";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { validateContracts, validateDispatchFiles } from "./contracts.mjs";
|
|
6
7
|
import { parseFrontmatter } from "./frontmatter.mjs";
|
|
7
8
|
import { deriveState, planNext, chooseIntentTarget } from "./routing.mjs";
|
|
8
9
|
|
|
@@ -35,7 +36,7 @@ function refsFor(reads,state,a,ticket){const refs=[];const push=x=>{if(x&&!refs.
|
|
|
35
36
|
function revisions(state,a,ticket){const spec=selectedSpec(state,a,ticket);return{product:Number(state.product.revision??0),engineering:Number(state.planning.revision??0),spec:Number(spec?.meta?.revision??0),ticket:Number(ticket?a.tickets?.[ticket]?.meta?.revision??0:0)}}
|
|
36
37
|
function basis(state,a,ticket){const spec=selectedSpec(state,a,ticket);return{product_digest:a.product?.digest??null,engineering_digest:a.engineering?.digest??null,spec_digest:spec?.digest??null,ticket_digest:ticket?a.tickets?.[ticket]?.digest??null:null,ticket_status:ticket?state.tickets?.[ticket]??null:null}}
|
|
37
38
|
function modulesFor(kernel,role,policy,a,ticket){const names=[...(policy.required_modules??[])];if(policy.ticket_modules&&ticket)for(const x of a.tickets?.[ticket]?.meta?.expertise??[])names.push(x);return[...new Set(names)].filter(name=>kernel.modules[name]?.usable_by?.includes(role)).map(name=>kernel.modules[name].path)}
|
|
38
|
-
function handoff(kernel,framework,state,a,repo,workflow,ticket,intent){const wf=kernel.workflows[workflow],policy=kernel.handoffs[workflow],role=kernel.roles[wf?.role];if(!wf||!policy||!role)throw new Error(`no handoff contract for ${workflow}`);for(const
|
|
39
|
+
function handoff(kernel,framework,state,a,repo,workflow,ticket,intent){const wf=kernel.workflows[workflow],policy=kernel.handoffs[workflow],role=kernel.roles[wf?.role];if(!wf||!policy||!role)throw new Error(`no handoff contract for ${workflow}`);for(const kind of ["reads","writes"])for(const artifact of policy[kind]??[])if(!role[kind].includes(artifact))throw new Error(`workflow ${workflow} ${kind} unauthorized artifact ${artifact}`);return{schema:"yaaw.handoff/v4",framework:frameworkBasis(framework),role:wf.role,workflow,operation:wf.operation,instruction:{document:role.document,operation:wf.operation},desired_intent:intent?.desired_outcome??null,active_artifact:policy.active_artifact??null,ticket:ticket??null,references:refsFor(policy.reads,state,a,ticket),reads:policy.reads,writes:policy.writes,forbidden_writes:Object.keys(kernel.artifacts).filter(x=>!role.writes.includes(x)),revisions:revisions(state,a,ticket),basis:basis(state,a,ticket),expertise:modulesFor(kernel,wf.role,policy,a,ticket),host_expertise:[],expected_output:policy.expected_output,result_vocabulary:policy.result_vocabulary,repository_requirement:wf.repository_requirement,repository:repo}}
|
|
39
40
|
function repoAllowed(requirement,repo){if(requirement==="NONE")return true;if(requirement==="INSPECT")return repo?.status==="READY"||repo?.status==="UNVERSIONED";return repo?.status==="READY"}
|
|
40
41
|
function patchStatus(text,status){const lines=String(text).split(/\r?\n/);if(lines[0]?.trim()!=="---")throw new Error("ticket missing frontmatter");const end=lines.slice(1).findIndex(x=>x.trim()==="---");if(end<0)throw new Error("ticket missing closing frontmatter");const idx=lines.slice(1,end+1).findIndex(x=>/^status:\s*/.test(x));if(idx<0)throw new Error("ticket missing status");lines[idx+1]=`status: ${status}`;return lines.join("\n")}
|
|
41
42
|
async function observed(root,framework,repo,state,a,reconciliation){const value={schema:"yaaw.observed-state/v4",framework:{...frameworkBasis(framework),modified:framework.modified??[],missing:framework.missing??[],local_overrides:framework.local_overrides??[],repair_required:Boolean(framework.repair_required)},repository:repo,derived:state,reconstructable:true,artifacts:{product:a.product,engineering:a.engineering,specs:a.specs,tickets:a.tickets,reviews:a.reviews,evidence:a.evidence,research:a.research,rules:a.rules},inconsistencies:[],candidate_actions:[],reconciliation};await mkdir(join(root,".yaaw-core","runtime"),{recursive:true});await writeFile(join(root,".yaaw-core","runtime","observed-state.json"),JSON.stringify(value,null,2)+"\n");return value}
|
|
@@ -43,15 +44,23 @@ async function observed(root,framework,repo,state,a,reconciliation){const value=
|
|
|
43
44
|
async function main(){
|
|
44
45
|
const opt=argsOf(process.argv.slice(2)),root=resolve(opt.workspace),system=join(root,".yaaw-core","system"),runtime=join(root,".yaaw-core","runtime"),kernel=JSON.parse(await readFile(join(system,"kernel.yaml"),"utf8"));
|
|
45
46
|
const integrity=execJson(join(system,"engine","integrity.mjs"),root);if(integrity.status!=="HEALTHY"){console.log(JSON.stringify({type:"FRAMEWORK_STOP",reason:`FRAMEWORK_${integrity.status}`,framework:integrity}));return}
|
|
47
|
+
const contractErrors=validateContracts(kernel);
|
|
48
|
+
if(contractErrors.length){
|
|
49
|
+
const intent=await json(join(runtime,"intent.json")),sourceSkill=opt.invoke??intent?.source_skill??null,requested=kernel.skills[sourceSkill]?.requested_workflow??intent?.requested_workflow??null;
|
|
50
|
+
const subject=contractErrors[0].split(": ")[0],workflow=kernel.workflows[subject]?subject:requested??subject;
|
|
51
|
+
throw Object.assign(new Error(contractErrors.join("; ")),{diagnostic:{classification:"FRAMEWORK_DEFECT_OR_DAMAGED_INSTALLATION",workflow,requested_workflow:requested,source_skill:sourceSkill,routing_source:sourceSkill?"PUBLIC_SKILL_INTENT":"DETERMINISTIC_ROUTER",owning_role:kernel.workflows[workflow]?.role??null,framework_version:integrity.package_version,contract_type:"DISPATCH_CONTRACT",recommendation:"Update or repair YAAW-SE through the supported installer; preserve project memory."}});
|
|
52
|
+
}
|
|
46
53
|
const repo=execJson(join(system,"engine","repository-identity.mjs"),root),a=await inspectArtifacts(root),state=deriveState(a);
|
|
47
54
|
let intent=await json(join(runtime,"intent.json"));if(opt.invoke)intent=await seedIntent(root,kernel,opt.invoke,state,a);
|
|
48
55
|
const planned=planNext(kernel,state,a,repo,intent),reconciliation=planned.type==="RECONCILE_REQUIRED"?planned.reconciliation:null;await observed(root,integrity,repo,state,a,reconciliation);
|
|
49
56
|
if(opt.inspect){console.log(JSON.stringify({type:"INSPECTED",state,reconciliation}));return}
|
|
50
57
|
if(opt.reconcile){if(!reconciliation){console.log(JSON.stringify({type:"NO_RECONCILIATION"}));return}if(reconciliation.kind==="BLOCKED"){console.log(JSON.stringify({type:"BLOCKED",reason:reconciliation.reason}));return}const ticket=a.tickets?.[reconciliation.subject];if(!ticket)throw new Error(`reconciliation ticket missing: ${reconciliation.subject}`);const ticketPath=join(root,ticket.path),raw=await readFile(ticketPath,"utf8");await writeFile(ticketPath,patchStatus(raw,reconciliation.to),"utf8");for(const file of ["observed-state.json","handoff.json"])try{await writeFile(join(runtime,file),"")}catch{}console.log(JSON.stringify({type:"RECONCILED",reconciliation}));return}
|
|
51
|
-
if(opt.check){const old=await json(join(runtime,"handoff.json"));if(!old){console.log(JSON.stringify({type:"HANDOFF_STALE",reason:"missing handoff"}));return}const candidate=handoff(kernel,integrity,state,a,repo,old.workflow,old.ticket,intent);console.log(JSON.stringify(same(old,candidate)?{type:"HANDOFF_FRESH"}:{type:"HANDOFF_STALE",reason:"durable basis changed"}));return}
|
|
58
|
+
if(opt.check){const old=await json(join(runtime,"handoff.json"));if(!old){console.log(JSON.stringify({type:"HANDOFF_STALE",reason:"missing handoff"}));return}const candidate=handoff(kernel,integrity,state,a,repo,old.workflow,old.ticket,intent);const fileErrors=await validateDispatchFiles(system,kernel,old.workflow,candidate.expertise);if(fileErrors.length)throw new Error(fileErrors.join("; "));console.log(JSON.stringify(same(old,candidate)?{type:"HANDOFF_FRESH"}:{type:"HANDOFF_STALE",reason:"durable basis changed"}));return}
|
|
52
59
|
if(planned.type!=="ROUTE"){if(planned.type!=="RECONCILE_REQUIRED")try{await writeFile(join(runtime,"handoff.json"),"")}catch{}console.log(JSON.stringify(planned));return}
|
|
53
60
|
if(planned.terminal){console.log(JSON.stringify({type:"TERMINAL",terminal:planned.terminal}));return}if(planned.blocked){console.log(JSON.stringify({type:"BLOCKED",reason:planned.blocked,ticket:planned.ticket??null}));return}
|
|
54
61
|
const wf=kernel.workflows[planned.workflow];if(!wf)throw new Error(`unknown workflow ${planned.workflow}`);if(wf.role==="orchestrator"){console.log(JSON.stringify({type:"ROOT_ACTION",workflow:planned.workflow,ticket:planned.ticket??null}));return}if(!repoAllowed(wf.repository_requirement,repo)){console.log(JSON.stringify({type:"BLOCKED",reason:"PRECONDITION_UNSATISFIED:REPOSITORY_IDENTITY_UNAVAILABLE",workflow:planned.workflow}));return}
|
|
55
|
-
const h=handoff(kernel,integrity,state,a,repo,planned.workflow,planned.ticket??null,intent);
|
|
62
|
+
const h=handoff(kernel,integrity,state,a,repo,planned.workflow,planned.ticket??null,intent);
|
|
63
|
+
const fileErrors=await validateDispatchFiles(system,kernel,planned.workflow,h.expertise);if(fileErrors.length)throw Object.assign(new Error(fileErrors.join("; ")),{diagnostic:{classification:"FRAMEWORK_DEFECT_OR_DAMAGED_INSTALLATION",workflow:planned.workflow,source_skill:intent?.source_skill??null,owning_role:h.role,framework_version:integrity.package_version,contract_type:"INSTRUCTION_OR_MODULE_FILE",recommendation:"Update or repair YAAW-SE through the supported installer; preserve project memory."}});
|
|
64
|
+
await mkdir(runtime,{recursive:true});await writeFile(join(runtime,"handoff.json"),JSON.stringify(h,null,2)+"\n");console.log(JSON.stringify({type:"DISPATCH_READY",workflow:planned.workflow,role:h.role,operation:h.operation,ticket:h.ticket,handoff:rel(root,join(runtime,"handoff.json"))}));
|
|
56
65
|
}
|
|
57
|
-
main().catch(error=>{process.stdout.write(JSON.stringify({type:"FRAMEWORK_STOP",reason:"FRAMEWORK_CONTRACT_INCONSISTENCY",error:String(error?.
|
|
66
|
+
main().catch(error=>{process.stdout.write(JSON.stringify({type:"FRAMEWORK_STOP",reason:"FRAMEWORK_CONTRACT_INCONSISTENCY",error:String(error?.message??error),...(error?.diagnostic?{diagnostic:error.diagnostic}:{})})+"\n");process.exitCode=2});
|
|
@@ -76,6 +76,9 @@
|
|
|
76
76
|
"orchestration.recover-interruption": {"role":"orchestrator","operation":"recover-interruption","repository_requirement":"IDENTITY","external_research":"forbidden","execution_context":"ROOT_ONLY"}
|
|
77
77
|
},
|
|
78
78
|
"handoffs": {
|
|
79
|
+
"prd.revise": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Human-approved revised product meaning, incremented semantic revision and provenance; READY or unresolved product questions. Downstream staleness is derived by Orchestrator.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
80
|
+
"prd.refine": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Clarified product text preserving accepted meaning and semantic revision, or a typed product question/stop.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
81
|
+
"planning.readiness-review": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","spec","ticket","project_rule","repository"],"writes":["engineering"],"required_modules":[],"ticket_modules":false,"expected_output":"Current-frontier readiness result, source revisions, reason and evidence persisted only in engineering.md.","result_vocabulary":["PASS","MISSING_DECISIONS","PRODUCT_GAP","REPLAN","BLOCKED"]},
|
|
79
82
|
"prd.route": {"active_artifact":"product","reads":["product"],"writes":["product"],"required_modules":[],"ticket_modules":false,"expected_output":"Current product intent or a typed stop.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","BLOCKED"]},
|
|
80
83
|
"planning.route": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","spec","ticket","project_rule","repository"],"writes":["engineering","engineering_research","spec","ticket","project_rule"],"required_modules":[],"ticket_modules":false,"expected_output":"Current engineering frontier readiness or one typed planning result.","result_vocabulary":["READY","HUMAN_INPUT_REQUIRED","REPLAN","BLOCKED"]},
|
|
81
84
|
"planning.create-spec": {"active_artifact":"engineering","reads":["product","engineering","engineering_research","project_rule","repository"],"writes":["spec"],"required_modules":[],"ticket_modules":false,"expected_output":"One accepted specification or a typed prerequisite/stop.","result_vocabulary":["SUCCESS","PRECONDITION_UNSATISFIED","BLOCKED"]},
|
|
@@ -15,13 +15,13 @@ Initialize `product.md` from the canonical template only when missing; never ove
|
|
|
15
15
|
Challenge only material contradictions, ambiguous terminology, unsupported assumptions, premature abstractions, important flows/failures/edge cases, and scope/non-goal conflicts. Exclude engineering-only questions and questions whose prerequisites remain unresolved. Ask at most 10 high-leverage questions; options/recommendation are optional and free-form answers are first-class. Stop for human input.
|
|
16
16
|
|
|
17
17
|
## Operation: record-decisions (`prd.record-decisions`)
|
|
18
|
-
Interpret only what the answer supports. Persist conclusions in the relevant product sections, not the question/challenge transcript. Remove settled questions, preserve corrections/non-goals, and increment product revision only when accepted product meaning materially changes. Recompute contradictions/questions and
|
|
18
|
+
Interpret only what the answer supports. Persist conclusions in the relevant product sections, not the question/challenge transcript. Remove settled questions, preserve corrections/non-goals, and increment product revision only when accepted product meaning materially changes. Recompute contradictions/questions and expose the changed product basis for downstream invalidation. Orchestrator derives staleness; Planner alone revises engineering/spec/ticket contracts.
|
|
19
19
|
|
|
20
20
|
## Operation: revise (`prd.revise`)
|
|
21
|
-
Identify the exact old requirement and requested new meaning. Stress-test impact on behavior, scope, constraints, non-goals, and newly exposed product questions without drifting into engineering design. Update `product.md`, increment revision, record provenance
|
|
21
|
+
Identify the exact old requirement and requested new meaning. Stress-test impact on behavior, scope, constraints, non-goals, and newly exposed product questions without drifting into engineering design. Update only `product.md`, increment revision for a material accepted meaning change, and record provenance. The changed product revision invalidates downstream authority through Orchestrator observation/reconciliation; do not mutate engineering, specifications, or tickets. Leave product draft if material questions remain; otherwise run readiness. Return `READY`, `HUMAN_INPUT_REQUIRED`, or `BLOCKED` for the dispatched invocation.
|
|
22
22
|
|
|
23
23
|
## Operation: refine (`prd.refine`)
|
|
24
|
-
Improve clarity, organization, deduplication, and completeness without changing accepted semantic meaning or product revision. If the edit changes behavior, scope, constraints, or non-goals—or exposes a genuine semantic contradiction—
|
|
24
|
+
Improve clarity, organization, deduplication, and completeness without changing accepted semantic meaning or product revision. If the edit changes behavior, scope, constraints, or non-goals—or exposes a genuine semantic contradiction—stop with `HUMAN_INPUT_REQUIRED` for explicit product revision authority instead of silently changing meaning. Return `READY` only when clarity work is complete and product readiness holds; otherwise return `HUMAN_INPUT_REQUIRED` or `BLOCKED`.
|
|
25
25
|
|
|
26
26
|
## Operation: readiness (`prd.readiness`)
|
|
27
27
|
Ask whether a fresh Planner can understand goal, users, expected behavior, scope, constraints, non-goals, and remaining product unknowns without the original chat, and whether any material current-frontier ambiguity would force invented product intent. Return `READY`, `NEEDS_QUESTIONS`, or `BLOCKED`. On READY, persist `status: ready` in `product.md`. Future fog is allowed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaaw-se",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "Artifact-first autonomous software engineering workflow installer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
"prepack": "npm run test && npm run build",
|
|
29
29
|
"smoke:tarball": "npm run build && node scripts/smoke_npm_tarball.mjs",
|
|
30
30
|
"smoke:update": "npm run build && node scripts/smoke_npm_update.mjs",
|
|
31
|
-
"validate:schemas": "node scripts/validate_schemas.mjs"
|
|
31
|
+
"validate:schemas": "node scripts/validate_schemas.mjs",
|
|
32
|
+
"validate:contracts": "node scripts/validate_contracts.mjs .yaaw-core/system skills",
|
|
33
|
+
"smoke:dispatch-update": "npm run build && node scripts/smoke_dispatch_update.mjs",
|
|
34
|
+
"audit:workflows": "node scripts/audit_workflow_contracts.mjs .yaaw-core/system skills"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
37
|
"@clack/prompts": "1.8.1",
|