@williambeto/ai-workflow 2.7.1 → 2.8.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/CHANGELOG.md +69 -0
- package/README.md +3 -2
- package/bin/ai-workflow.js +1203 -301
- package/bin/ai-workflow.js.map +1 -1
- package/{chunk-W4RTQWVQ.js → chunk-4FI5ODAM.js} +879 -759
- package/chunk-AINIR25D.js +120 -0
- package/chunk-AY33SA5W.js +46 -0
- package/{evidence-validator-76ZQQYDU.js → chunk-FNT7DN3N.js} +22 -2
- package/{chunk-XW747GIG.js → chunk-H7GIKXFO.js} +339 -14
- package/{chunk-BDZPUAEX.js → chunk-LD7EHAU2.js} +352 -39
- package/chunk-YOBY5C72.js +76 -0
- package/core/index.d.ts +51 -1
- package/core/index.js +5 -3
- package/dist-assets/agents/atlas.md +25 -6
- package/dist-assets/commands/atlas.md +7 -2
- package/dist-assets/docs/QUICKSTART.md +10 -1
- package/dist-assets/docs/cli-reference.md +47 -2
- package/dist-assets/docs/compatibility/provider-usage.md +16 -1
- package/dist-assets/docs/compatibility/runtime-matrix.md +13 -2
- package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +11 -1
- package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +16 -0
- package/dist-assets/schemas/README.md +4 -0
- package/dist-assets/schemas/approval-receipt.schema.json +35 -0
- package/dist-assets/schemas/evidence.schema.json +93 -1
- package/evidence-validator-HS3NTWAB.js +8 -0
- package/package.json +17 -2
- package/skill-4MEGJ3DO.js +211 -0
- package/skill-frontmatter-linter-FMJADOK4.js +14 -0
- package/{validate-A46WUBVZ.js → validate-F3ZH63LI.js} +3 -3
package/bin/ai-workflow.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
BranchGate,
|
|
4
3
|
COMPLETION_STATUS_TEXT,
|
|
5
4
|
DelegationController,
|
|
6
5
|
EvidenceLedger,
|
|
@@ -15,24 +14,360 @@ import {
|
|
|
15
14
|
discoverPackageFiles,
|
|
16
15
|
getFullAgentContent,
|
|
17
16
|
getFullSkillFiles,
|
|
17
|
+
getPackageRoot,
|
|
18
18
|
getPackageVersion,
|
|
19
19
|
getWorkflowProfile,
|
|
20
20
|
isRecoverableGateFailure,
|
|
21
21
|
isTerminalFailure,
|
|
22
22
|
readPackageFile,
|
|
23
23
|
resolveWorkflowProfile
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-4FI5ODAM.js";
|
|
25
|
+
import {
|
|
26
|
+
EvidenceValidator,
|
|
27
|
+
require_ajv,
|
|
28
|
+
require_codegen
|
|
29
|
+
} from "../chunk-FNT7DN3N.js";
|
|
25
30
|
import {
|
|
26
31
|
EvidenceCollector,
|
|
27
32
|
QualityGuard,
|
|
28
|
-
ValidationPlanner
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import
|
|
33
|
+
ValidationPlanner,
|
|
34
|
+
parseDeliveryOutcomeClaim
|
|
35
|
+
} from "../chunk-LD7EHAU2.js";
|
|
36
|
+
import {
|
|
37
|
+
DeliveryDecisionEngine
|
|
38
|
+
} from "../chunk-H7GIKXFO.js";
|
|
39
|
+
import {
|
|
40
|
+
createManagedBackup,
|
|
41
|
+
createManagedPathBackup
|
|
42
|
+
} from "../chunk-AY33SA5W.js";
|
|
43
|
+
import {
|
|
44
|
+
BranchGate
|
|
45
|
+
} from "../chunk-AINIR25D.js";
|
|
46
|
+
import {
|
|
47
|
+
__commonJS,
|
|
48
|
+
__toESM
|
|
49
|
+
} from "../chunk-5WRI5ZAA.js";
|
|
50
|
+
|
|
51
|
+
// node_modules/ajv-formats/dist/formats.js
|
|
52
|
+
var require_formats = __commonJS({
|
|
53
|
+
"node_modules/ajv-formats/dist/formats.js"(exports) {
|
|
54
|
+
"use strict";
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.formatNames = exports.fastFormats = exports.fullFormats = void 0;
|
|
57
|
+
function fmtDef(validate, compare) {
|
|
58
|
+
return { validate, compare };
|
|
59
|
+
}
|
|
60
|
+
exports.fullFormats = {
|
|
61
|
+
// date: http://tools.ietf.org/html/rfc3339#section-5.6
|
|
62
|
+
date: fmtDef(date, compareDate),
|
|
63
|
+
// date-time: http://tools.ietf.org/html/rfc3339#section-5.6
|
|
64
|
+
time: fmtDef(getTime(true), compareTime),
|
|
65
|
+
"date-time": fmtDef(getDateTime(true), compareDateTime),
|
|
66
|
+
"iso-time": fmtDef(getTime(), compareIsoTime),
|
|
67
|
+
"iso-date-time": fmtDef(getDateTime(), compareIsoDateTime),
|
|
68
|
+
// duration: https://tools.ietf.org/html/rfc3339#appendix-A
|
|
69
|
+
duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/,
|
|
70
|
+
uri,
|
|
71
|
+
"uri-reference": /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i,
|
|
72
|
+
// uri-template: https://tools.ietf.org/html/rfc6570
|
|
73
|
+
"uri-template": /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i,
|
|
74
|
+
// For the source: https://gist.github.com/dperini/729294
|
|
75
|
+
// For test cases: https://mathiasbynens.be/demo/url-regex
|
|
76
|
+
url: /^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu,
|
|
77
|
+
email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
|
|
78
|
+
hostname: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i,
|
|
79
|
+
// optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
|
|
80
|
+
ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,
|
|
81
|
+
ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i,
|
|
82
|
+
regex,
|
|
83
|
+
// uuid: http://tools.ietf.org/html/rfc4122
|
|
84
|
+
uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,
|
|
85
|
+
// JSON-pointer: https://tools.ietf.org/html/rfc6901
|
|
86
|
+
// uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A
|
|
87
|
+
"json-pointer": /^(?:\/(?:[^~/]|~0|~1)*)*$/,
|
|
88
|
+
"json-pointer-uri-fragment": /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,
|
|
89
|
+
// relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00
|
|
90
|
+
"relative-json-pointer": /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/,
|
|
91
|
+
// the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types
|
|
92
|
+
// byte: https://github.com/miguelmota/is-base64
|
|
93
|
+
byte,
|
|
94
|
+
// signed 32 bit integer
|
|
95
|
+
int32: { type: "number", validate: validateInt32 },
|
|
96
|
+
// signed 64 bit integer
|
|
97
|
+
int64: { type: "number", validate: validateInt64 },
|
|
98
|
+
// C-type float
|
|
99
|
+
float: { type: "number", validate: validateNumber },
|
|
100
|
+
// C-type double
|
|
101
|
+
double: { type: "number", validate: validateNumber },
|
|
102
|
+
// hint to the UI to hide input strings
|
|
103
|
+
password: true,
|
|
104
|
+
// unchecked string payload
|
|
105
|
+
binary: true
|
|
106
|
+
};
|
|
107
|
+
exports.fastFormats = {
|
|
108
|
+
...exports.fullFormats,
|
|
109
|
+
date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate),
|
|
110
|
+
time: fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareTime),
|
|
111
|
+
"date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareDateTime),
|
|
112
|
+
"iso-time": fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoTime),
|
|
113
|
+
"iso-date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoDateTime),
|
|
114
|
+
// uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js
|
|
115
|
+
uri: /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i,
|
|
116
|
+
"uri-reference": /^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i,
|
|
117
|
+
// email (sources from jsen validator):
|
|
118
|
+
// http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
|
|
119
|
+
// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation')
|
|
120
|
+
email: /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i
|
|
121
|
+
};
|
|
122
|
+
exports.formatNames = Object.keys(exports.fullFormats);
|
|
123
|
+
function isLeapYear(year) {
|
|
124
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
125
|
+
}
|
|
126
|
+
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
127
|
+
var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
128
|
+
function date(str) {
|
|
129
|
+
const matches = DATE.exec(str);
|
|
130
|
+
if (!matches)
|
|
131
|
+
return false;
|
|
132
|
+
const year = +matches[1];
|
|
133
|
+
const month = +matches[2];
|
|
134
|
+
const day = +matches[3];
|
|
135
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
|
|
136
|
+
}
|
|
137
|
+
function compareDate(d1, d2) {
|
|
138
|
+
if (!(d1 && d2))
|
|
139
|
+
return void 0;
|
|
140
|
+
if (d1 > d2)
|
|
141
|
+
return 1;
|
|
142
|
+
if (d1 < d2)
|
|
143
|
+
return -1;
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
147
|
+
function getTime(strictTimeZone) {
|
|
148
|
+
return function time(str) {
|
|
149
|
+
const matches = TIME.exec(str);
|
|
150
|
+
if (!matches)
|
|
151
|
+
return false;
|
|
152
|
+
const hr = +matches[1];
|
|
153
|
+
const min = +matches[2];
|
|
154
|
+
const sec = +matches[3];
|
|
155
|
+
const tz = matches[4];
|
|
156
|
+
const tzSign = matches[5] === "-" ? -1 : 1;
|
|
157
|
+
const tzH = +(matches[6] || 0);
|
|
158
|
+
const tzM = +(matches[7] || 0);
|
|
159
|
+
if (tzH > 23 || tzM > 59 || strictTimeZone && !tz)
|
|
160
|
+
return false;
|
|
161
|
+
if (hr <= 23 && min <= 59 && sec < 60)
|
|
162
|
+
return true;
|
|
163
|
+
const utcMin = min - tzM * tzSign;
|
|
164
|
+
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0);
|
|
165
|
+
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function compareTime(s1, s2) {
|
|
169
|
+
if (!(s1 && s2))
|
|
170
|
+
return void 0;
|
|
171
|
+
const t1 = (/* @__PURE__ */ new Date("2020-01-01T" + s1)).valueOf();
|
|
172
|
+
const t2 = (/* @__PURE__ */ new Date("2020-01-01T" + s2)).valueOf();
|
|
173
|
+
if (!(t1 && t2))
|
|
174
|
+
return void 0;
|
|
175
|
+
return t1 - t2;
|
|
176
|
+
}
|
|
177
|
+
function compareIsoTime(t1, t2) {
|
|
178
|
+
if (!(t1 && t2))
|
|
179
|
+
return void 0;
|
|
180
|
+
const a1 = TIME.exec(t1);
|
|
181
|
+
const a2 = TIME.exec(t2);
|
|
182
|
+
if (!(a1 && a2))
|
|
183
|
+
return void 0;
|
|
184
|
+
t1 = a1[1] + a1[2] + a1[3];
|
|
185
|
+
t2 = a2[1] + a2[2] + a2[3];
|
|
186
|
+
if (t1 > t2)
|
|
187
|
+
return 1;
|
|
188
|
+
if (t1 < t2)
|
|
189
|
+
return -1;
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
var DATE_TIME_SEPARATOR = /t|\s/i;
|
|
193
|
+
function getDateTime(strictTimeZone) {
|
|
194
|
+
const time = getTime(strictTimeZone);
|
|
195
|
+
return function date_time(str) {
|
|
196
|
+
const dateTime = str.split(DATE_TIME_SEPARATOR);
|
|
197
|
+
return dateTime.length === 2 && date(dateTime[0]) && time(dateTime[1]);
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function compareDateTime(dt1, dt2) {
|
|
201
|
+
if (!(dt1 && dt2))
|
|
202
|
+
return void 0;
|
|
203
|
+
const d1 = new Date(dt1).valueOf();
|
|
204
|
+
const d2 = new Date(dt2).valueOf();
|
|
205
|
+
if (!(d1 && d2))
|
|
206
|
+
return void 0;
|
|
207
|
+
return d1 - d2;
|
|
208
|
+
}
|
|
209
|
+
function compareIsoDateTime(dt1, dt2) {
|
|
210
|
+
if (!(dt1 && dt2))
|
|
211
|
+
return void 0;
|
|
212
|
+
const [d1, t1] = dt1.split(DATE_TIME_SEPARATOR);
|
|
213
|
+
const [d2, t2] = dt2.split(DATE_TIME_SEPARATOR);
|
|
214
|
+
const res = compareDate(d1, d2);
|
|
215
|
+
if (res === void 0)
|
|
216
|
+
return void 0;
|
|
217
|
+
return res || compareTime(t1, t2);
|
|
218
|
+
}
|
|
219
|
+
var NOT_URI_FRAGMENT = /\/|:/;
|
|
220
|
+
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
|
|
221
|
+
function uri(str) {
|
|
222
|
+
return NOT_URI_FRAGMENT.test(str) && URI.test(str);
|
|
223
|
+
}
|
|
224
|
+
var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
225
|
+
function byte(str) {
|
|
226
|
+
BYTE.lastIndex = 0;
|
|
227
|
+
return BYTE.test(str);
|
|
228
|
+
}
|
|
229
|
+
var MIN_INT32 = -(2 ** 31);
|
|
230
|
+
var MAX_INT32 = 2 ** 31 - 1;
|
|
231
|
+
function validateInt32(value) {
|
|
232
|
+
return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32;
|
|
233
|
+
}
|
|
234
|
+
function validateInt64(value) {
|
|
235
|
+
return Number.isInteger(value);
|
|
236
|
+
}
|
|
237
|
+
function validateNumber() {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
var Z_ANCHOR = /[^\\]\\Z/;
|
|
241
|
+
function regex(str) {
|
|
242
|
+
if (Z_ANCHOR.test(str))
|
|
243
|
+
return false;
|
|
244
|
+
try {
|
|
245
|
+
new RegExp(str);
|
|
246
|
+
return true;
|
|
247
|
+
} catch (e) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// node_modules/ajv-formats/dist/limit.js
|
|
255
|
+
var require_limit = __commonJS({
|
|
256
|
+
"node_modules/ajv-formats/dist/limit.js"(exports) {
|
|
257
|
+
"use strict";
|
|
258
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
259
|
+
exports.formatLimitDefinition = void 0;
|
|
260
|
+
var ajv_1 = require_ajv();
|
|
261
|
+
var codegen_1 = require_codegen();
|
|
262
|
+
var ops = codegen_1.operators;
|
|
263
|
+
var KWDs = {
|
|
264
|
+
formatMaximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT },
|
|
265
|
+
formatMinimum: { okStr: ">=", ok: ops.GTE, fail: ops.LT },
|
|
266
|
+
formatExclusiveMaximum: { okStr: "<", ok: ops.LT, fail: ops.GTE },
|
|
267
|
+
formatExclusiveMinimum: { okStr: ">", ok: ops.GT, fail: ops.LTE }
|
|
268
|
+
};
|
|
269
|
+
var error = {
|
|
270
|
+
message: ({ keyword, schemaCode }) => (0, codegen_1.str)`should be ${KWDs[keyword].okStr} ${schemaCode}`,
|
|
271
|
+
params: ({ keyword, schemaCode }) => (0, codegen_1._)`{comparison: ${KWDs[keyword].okStr}, limit: ${schemaCode}}`
|
|
272
|
+
};
|
|
273
|
+
exports.formatLimitDefinition = {
|
|
274
|
+
keyword: Object.keys(KWDs),
|
|
275
|
+
type: "string",
|
|
276
|
+
schemaType: "string",
|
|
277
|
+
$data: true,
|
|
278
|
+
error,
|
|
279
|
+
code(cxt) {
|
|
280
|
+
const { gen, data, schemaCode, keyword, it } = cxt;
|
|
281
|
+
const { opts, self } = it;
|
|
282
|
+
if (!opts.validateFormats)
|
|
283
|
+
return;
|
|
284
|
+
const fCxt = new ajv_1.KeywordCxt(it, self.RULES.all.format.definition, "format");
|
|
285
|
+
if (fCxt.$data)
|
|
286
|
+
validate$DataFormat();
|
|
287
|
+
else
|
|
288
|
+
validateFormat();
|
|
289
|
+
function validate$DataFormat() {
|
|
290
|
+
const fmts = gen.scopeValue("formats", {
|
|
291
|
+
ref: self.formats,
|
|
292
|
+
code: opts.code.formats
|
|
293
|
+
});
|
|
294
|
+
const fmt = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
|
|
295
|
+
cxt.fail$data((0, codegen_1.or)((0, codegen_1._)`typeof ${fmt} != "object"`, (0, codegen_1._)`${fmt} instanceof RegExp`, (0, codegen_1._)`typeof ${fmt}.compare != "function"`, compareCode(fmt)));
|
|
296
|
+
}
|
|
297
|
+
function validateFormat() {
|
|
298
|
+
const format = fCxt.schema;
|
|
299
|
+
const fmtDef = self.formats[format];
|
|
300
|
+
if (!fmtDef || fmtDef === true)
|
|
301
|
+
return;
|
|
302
|
+
if (typeof fmtDef != "object" || fmtDef instanceof RegExp || typeof fmtDef.compare != "function") {
|
|
303
|
+
throw new Error(`"${keyword}": format "${format}" does not define "compare" function`);
|
|
304
|
+
}
|
|
305
|
+
const fmt = gen.scopeValue("formats", {
|
|
306
|
+
key: format,
|
|
307
|
+
ref: fmtDef,
|
|
308
|
+
code: opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(format)}` : void 0
|
|
309
|
+
});
|
|
310
|
+
cxt.fail$data(compareCode(fmt));
|
|
311
|
+
}
|
|
312
|
+
function compareCode(fmt) {
|
|
313
|
+
return (0, codegen_1._)`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
dependencies: ["format"]
|
|
317
|
+
};
|
|
318
|
+
var formatLimitPlugin = (ajv) => {
|
|
319
|
+
ajv.addKeyword(exports.formatLimitDefinition);
|
|
320
|
+
return ajv;
|
|
321
|
+
};
|
|
322
|
+
exports.default = formatLimitPlugin;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// node_modules/ajv-formats/dist/index.js
|
|
327
|
+
var require_dist = __commonJS({
|
|
328
|
+
"node_modules/ajv-formats/dist/index.js"(exports, module) {
|
|
329
|
+
"use strict";
|
|
330
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
331
|
+
var formats_1 = require_formats();
|
|
332
|
+
var limit_1 = require_limit();
|
|
333
|
+
var codegen_1 = require_codegen();
|
|
334
|
+
var fullName = new codegen_1.Name("fullFormats");
|
|
335
|
+
var fastName = new codegen_1.Name("fastFormats");
|
|
336
|
+
var formatsPlugin = (ajv, opts = { keywords: true }) => {
|
|
337
|
+
if (Array.isArray(opts)) {
|
|
338
|
+
addFormats2(ajv, opts, formats_1.fullFormats, fullName);
|
|
339
|
+
return ajv;
|
|
340
|
+
}
|
|
341
|
+
const [formats, exportName] = opts.mode === "fast" ? [formats_1.fastFormats, fastName] : [formats_1.fullFormats, fullName];
|
|
342
|
+
const list = opts.formats || formats_1.formatNames;
|
|
343
|
+
addFormats2(ajv, list, formats, exportName);
|
|
344
|
+
if (opts.keywords)
|
|
345
|
+
(0, limit_1.default)(ajv);
|
|
346
|
+
return ajv;
|
|
347
|
+
};
|
|
348
|
+
formatsPlugin.get = (name, mode = "full") => {
|
|
349
|
+
const formats = mode === "fast" ? formats_1.fastFormats : formats_1.fullFormats;
|
|
350
|
+
const f = formats[name];
|
|
351
|
+
if (!f)
|
|
352
|
+
throw new Error(`Unknown format "${name}"`);
|
|
353
|
+
return f;
|
|
354
|
+
};
|
|
355
|
+
function addFormats2(ajv, list, fs15, exportName) {
|
|
356
|
+
var _a;
|
|
357
|
+
var _b;
|
|
358
|
+
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
359
|
+
for (const f of list)
|
|
360
|
+
ajv.addFormat(f, fs15[f]);
|
|
361
|
+
}
|
|
362
|
+
module.exports = exports = formatsPlugin;
|
|
363
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
364
|
+
exports.default = formatsPlugin;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
32
367
|
|
|
33
368
|
// src/cli/commands/init.ts
|
|
34
|
-
import
|
|
35
|
-
import
|
|
369
|
+
import fs6 from "fs/promises";
|
|
370
|
+
import path8 from "path";
|
|
36
371
|
|
|
37
372
|
// src/core/install-plan.ts
|
|
38
373
|
import path from "path";
|
|
@@ -390,79 +725,38 @@ async function readJsonc(filePath) {
|
|
|
390
725
|
return JSON.parse(stripTrailingCommas(stripJsonComments(content)));
|
|
391
726
|
}
|
|
392
727
|
|
|
393
|
-
// src/core/backup.ts
|
|
394
|
-
import fs2 from "fs/promises";
|
|
395
|
-
import path3 from "path";
|
|
396
|
-
function escapeRelativePath(relativePath) {
|
|
397
|
-
return relativePath.replace(/[\\/]/g, "__");
|
|
398
|
-
}
|
|
399
|
-
async function createManagedBackup(filePath, { cwd, backupRoot, maxPerFile = 20 }) {
|
|
400
|
-
const relativePath = path3.relative(cwd, filePath);
|
|
401
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
402
|
-
const backupName = `${escapeRelativePath(relativePath)}.${stamp}.bak`;
|
|
403
|
-
const absoluteBackupRoot = path3.join(cwd, backupRoot);
|
|
404
|
-
const backupPath = path3.join(absoluteBackupRoot, backupName);
|
|
405
|
-
await fs2.mkdir(absoluteBackupRoot, { recursive: true });
|
|
406
|
-
await fs2.copyFile(filePath, backupPath);
|
|
407
|
-
const prefix = `${escapeRelativePath(relativePath)}.`;
|
|
408
|
-
const entries = await fs2.readdir(absoluteBackupRoot);
|
|
409
|
-
const matching = entries.filter((entry) => entry.startsWith(prefix) && entry.endsWith(".bak")).sort();
|
|
410
|
-
if (matching.length > maxPerFile) {
|
|
411
|
-
const excess = matching.slice(0, matching.length - maxPerFile);
|
|
412
|
-
await Promise.all(excess.map((entry) => fs2.unlink(path3.join(absoluteBackupRoot, entry))));
|
|
413
|
-
}
|
|
414
|
-
return backupPath;
|
|
415
|
-
}
|
|
416
|
-
async function createManagedPathBackup(targetPath, { cwd, backupRoot, maxPerFile = 20 }) {
|
|
417
|
-
const relativePath = path3.relative(cwd, targetPath);
|
|
418
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
419
|
-
const backupName = `${escapeRelativePath(relativePath)}.${stamp}.bak`;
|
|
420
|
-
const absoluteBackupRoot = path3.join(cwd, backupRoot);
|
|
421
|
-
const backupPath = path3.join(absoluteBackupRoot, backupName);
|
|
422
|
-
await fs2.mkdir(absoluteBackupRoot, { recursive: true });
|
|
423
|
-
await fs2.rename(targetPath, backupPath);
|
|
424
|
-
const prefix = `${escapeRelativePath(relativePath)}.`;
|
|
425
|
-
const entries = await fs2.readdir(absoluteBackupRoot);
|
|
426
|
-
const matching = entries.filter((entry) => entry.startsWith(prefix) && entry.endsWith(".bak")).sort();
|
|
427
|
-
if (matching.length > maxPerFile) {
|
|
428
|
-
const excess = matching.slice(0, matching.length - maxPerFile);
|
|
429
|
-
await Promise.all(excess.map((entry) => fs2.rm(path3.join(absoluteBackupRoot, entry), { recursive: true, force: true })));
|
|
430
|
-
}
|
|
431
|
-
return backupPath;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
728
|
// src/core/opencode-merge.ts
|
|
435
|
-
import
|
|
729
|
+
import path3 from "path";
|
|
436
730
|
var LEGACY_MARKER = "_aiWorkflowManaged";
|
|
437
731
|
var AGENTS = {
|
|
438
|
-
Atlas: { description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
439
|
-
Nexus: { description: "Discovery, requirement, scope, and specification owner" },
|
|
440
|
-
Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner" },
|
|
441
|
-
Astra: { description: "Implementation owner for incremental, scoped changes" },
|
|
442
|
-
Sage: { description: "Audit, validation, evidence, and quality gate owner" },
|
|
443
|
-
Phoenix: { description: "
|
|
732
|
+
Atlas: { description: "Router and workflow coordinator for AI Workflow Kit", mode: "primary" },
|
|
733
|
+
Nexus: { description: "Discovery, requirement, scope, and specification owner", mode: "all" },
|
|
734
|
+
Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner", mode: "all" },
|
|
735
|
+
Astra: { description: "Implementation owner for incremental, scoped workspace changes", mode: "all" },
|
|
736
|
+
Sage: { description: "Audit, validation, evidence, and quality gate owner", mode: "all" },
|
|
737
|
+
Phoenix: { description: "Bounded remediation and regression recovery owner", mode: "all" }
|
|
444
738
|
};
|
|
445
739
|
var SUBAGENTS = {
|
|
446
|
-
"Architecture-Specialist": { description: "
|
|
447
|
-
"Backend-Engineer": { description: "
|
|
448
|
-
"Deployment-Specialist": { description: "
|
|
449
|
-
"Design-Specialist": { description: "
|
|
450
|
-
"Docs-Engineer": { description: "
|
|
451
|
-
"Frontend-Engineer": { description: "
|
|
452
|
-
"Frontend-Design-System-Specialist": { description: "
|
|
453
|
-
"Full-Stack-Engineer": { description: "
|
|
454
|
-
"Token-Economist": { description: "
|
|
455
|
-
"PR-Manager": { description: "
|
|
456
|
-
"Discovery-Analyst": { description: "
|
|
457
|
-
"Product-Planner": { description: "
|
|
458
|
-
"Memory-Guardian": { description: "
|
|
459
|
-
"Prompt-Engineer": { description: "
|
|
460
|
-
"QA-Engineer": { description: "
|
|
461
|
-
"Refactoring-Specialist": { description: "
|
|
462
|
-
"Release-Specialist": { description: "
|
|
463
|
-
"SDD-Specialist": { description: "
|
|
464
|
-
"Technical-Leader": { description: "
|
|
465
|
-
"UI-UX-Engineer": { description: "
|
|
740
|
+
"Architecture-Specialist": { description: "Capability only: advise on the simplest safe architecture for current requirements", skill: "architecture" },
|
|
741
|
+
"Backend-Engineer": { description: "Capability only: advise on PHP, Node, or Python APIs and persistence", skill: "backend-development" },
|
|
742
|
+
"Deployment-Specialist": { description: "Capability only: analyze deployment, release, and production readiness", skill: "deployment" },
|
|
743
|
+
"Design-Specialist": { description: "Capability only: advise on simple and maintainable design principles", skill: "design-principles" },
|
|
744
|
+
"Docs-Engineer": { description: "Capability only: advise on documentation, READMEs, guides, and prompts", skill: "documentation" },
|
|
745
|
+
"Frontend-Engineer": { description: "Capability only: advise on UI, state, routing, and accessibility", skill: "frontend-development" },
|
|
746
|
+
"Frontend-Design-System-Specialist": { description: "Capability only: review CSS and HTML tokens, semantics, typography, and visual quality", skill: "frontend-design-system" },
|
|
747
|
+
"Full-Stack-Engineer": { description: "Capability only: analyze frontend and backend integration as one testable increment", skill: "full-stack-development" },
|
|
748
|
+
"Token-Economist": { description: "Capability only: analyze context size while preserving safe execution information", skill: "optimize-tokens" },
|
|
749
|
+
"PR-Manager": { description: "Capability only: plan or review small pull requests while preserving scope", skill: "pr-workflow" },
|
|
750
|
+
"Discovery-Analyst": { description: "Capability only: clarify ambiguous requests into actionable outcomes", skill: "product-discovery" },
|
|
751
|
+
"Product-Planner": { description: "Capability only: shape clarified needs into scoped requirements", skill: "product-planning" },
|
|
752
|
+
"Memory-Guardian": { description: "Capability only: advise on durable project decisions and recurring corrections", skill: "project-memory" },
|
|
753
|
+
"Prompt-Engineer": { description: "Capability only: advise on prompts for agents and workflows", skill: "prompt-engineer" },
|
|
754
|
+
"QA-Engineer": { description: "Capability only: design or review tests, acceptance criteria, and validation evidence", skill: "qa-workflow" },
|
|
755
|
+
"Refactoring-Specialist": { description: "Capability only: review structure and behavior-preserving simplifications", skill: "refactoring" },
|
|
756
|
+
"Release-Specialist": { description: "Capability only: analyze release readiness with explicit gates and evidence", skill: "release-workflow" },
|
|
757
|
+
"SDD-Specialist": { description: "Capability only: shape approved requirements into testable specifications", skill: "spec-driven-development" },
|
|
758
|
+
"Technical-Leader": { description: "Capability only: analyze technical decisions, architecture, and trade-offs", skill: "technical-leadership" },
|
|
759
|
+
"UI-UX-Engineer": { description: "Capability only: review usability and interface clarity", skill: "ui-ux-design" }
|
|
466
760
|
};
|
|
467
761
|
var COMMANDS = {
|
|
468
762
|
atlas: { description: "Decide safest next step and delegate to the appropriate workflow or agent", agent: "Atlas" },
|
|
@@ -482,18 +776,36 @@ var COMMANDS = {
|
|
|
482
776
|
};
|
|
483
777
|
function buildManagedConfig() {
|
|
484
778
|
const agent = {};
|
|
779
|
+
const coreTaskPermissions = Object.fromEntries([
|
|
780
|
+
["*", "deny"],
|
|
781
|
+
...Object.keys(AGENTS).filter((name) => name !== "Atlas").map((name) => [name, "allow"])
|
|
782
|
+
]);
|
|
783
|
+
const capabilityTaskPermissions = Object.fromEntries([
|
|
784
|
+
["*", "deny"],
|
|
785
|
+
...Object.keys(SUBAGENTS).map((name) => [name, "allow"])
|
|
786
|
+
]);
|
|
485
787
|
for (const [name, def] of Object.entries(AGENTS)) {
|
|
486
788
|
agent[name] = {
|
|
487
|
-
mode:
|
|
789
|
+
mode: def.mode,
|
|
488
790
|
description: def.description,
|
|
489
|
-
prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}
|
|
791
|
+
prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`,
|
|
792
|
+
permission: {
|
|
793
|
+
...name === "Atlas" ? { edit: "deny", bash: "ask" } : {},
|
|
794
|
+
...name === "Astra" ? { edit: "allow" } : {},
|
|
795
|
+
task: name === "Atlas" ? coreTaskPermissions : capabilityTaskPermissions
|
|
796
|
+
}
|
|
490
797
|
};
|
|
491
798
|
}
|
|
492
799
|
for (const [name, def] of Object.entries(SUBAGENTS)) {
|
|
493
800
|
agent[name] = {
|
|
494
801
|
mode: "subagent",
|
|
495
802
|
description: def.description,
|
|
496
|
-
prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}
|
|
803
|
+
prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}`,
|
|
804
|
+
permission: {
|
|
805
|
+
edit: "deny",
|
|
806
|
+
bash: "ask",
|
|
807
|
+
task: { "*": "deny" }
|
|
808
|
+
}
|
|
497
809
|
};
|
|
498
810
|
}
|
|
499
811
|
const command = {};
|
|
@@ -515,7 +827,7 @@ function buildManagedConfig() {
|
|
|
515
827
|
};
|
|
516
828
|
}
|
|
517
829
|
async function mergeOpencodeConfig(cwd, { force = false, backupRoot = ".ai-workflow-backups" } = {}) {
|
|
518
|
-
const targetPath =
|
|
830
|
+
const targetPath = path3.join(cwd, "opencode.jsonc");
|
|
519
831
|
const managed = buildManagedConfig();
|
|
520
832
|
if (!await exists(targetPath)) {
|
|
521
833
|
await writeFileSafe(targetPath, `${JSON.stringify(managed, null, 2)}
|
|
@@ -567,10 +879,10 @@ async function mergeOpencodeConfig(cwd, { force = false, backupRoot = ".ai-workf
|
|
|
567
879
|
}
|
|
568
880
|
|
|
569
881
|
// src/core/symlink-layout.ts
|
|
570
|
-
import
|
|
571
|
-
import
|
|
882
|
+
import fs2 from "fs/promises";
|
|
883
|
+
import path4 from "path";
|
|
572
884
|
function normalize(p) {
|
|
573
|
-
return p.split(
|
|
885
|
+
return p.split(path4.sep).join("/");
|
|
574
886
|
}
|
|
575
887
|
var LINKABLE_ROOTS = /* @__PURE__ */ new Set([
|
|
576
888
|
"opencode",
|
|
@@ -598,38 +910,38 @@ function buildSymlinkEntries({ templateFiles, installRoot = ".ai-workflow" }) {
|
|
|
598
910
|
}
|
|
599
911
|
async function isSymlinkTo(absoluteLinkPath, absoluteTargetPath) {
|
|
600
912
|
try {
|
|
601
|
-
const stat = await
|
|
913
|
+
const stat = await fs2.lstat(absoluteLinkPath);
|
|
602
914
|
if (!stat.isSymbolicLink()) return false;
|
|
603
|
-
const currentTarget = await
|
|
604
|
-
const resolved =
|
|
915
|
+
const currentTarget = await fs2.readlink(absoluteLinkPath);
|
|
916
|
+
const resolved = path4.resolve(path4.dirname(absoluteLinkPath), currentTarget);
|
|
605
917
|
return resolved === absoluteTargetPath;
|
|
606
918
|
} catch {
|
|
607
919
|
return false;
|
|
608
920
|
}
|
|
609
921
|
}
|
|
610
922
|
async function setupInternalSymlinks(cwd, installRoot = ".ai-workflow") {
|
|
611
|
-
const skillsLink =
|
|
612
|
-
const skillsTarget =
|
|
923
|
+
const skillsLink = path4.join(cwd, installRoot, "opencode/skills");
|
|
924
|
+
const skillsTarget = path4.join(cwd, installRoot, ".agents/skills");
|
|
613
925
|
if (!await exists2(skillsTarget)) return;
|
|
614
926
|
try {
|
|
615
|
-
const stat = await
|
|
927
|
+
const stat = await fs2.lstat(skillsLink).catch(() => null);
|
|
616
928
|
if (stat) {
|
|
617
929
|
if (stat.isSymbolicLink()) {
|
|
618
|
-
const current = await
|
|
930
|
+
const current = await fs2.readlink(skillsLink);
|
|
619
931
|
if (current === "../.agents/skills") return;
|
|
620
|
-
await
|
|
932
|
+
await fs2.unlink(skillsLink);
|
|
621
933
|
} else {
|
|
622
934
|
return;
|
|
623
935
|
}
|
|
624
936
|
}
|
|
625
937
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
626
|
-
await
|
|
938
|
+
await fs2.symlink("../.agents/skills", skillsLink, type);
|
|
627
939
|
} catch {
|
|
628
940
|
}
|
|
629
941
|
}
|
|
630
942
|
async function exists2(p) {
|
|
631
943
|
try {
|
|
632
|
-
await
|
|
944
|
+
await fs2.access(p);
|
|
633
945
|
return true;
|
|
634
946
|
} catch {
|
|
635
947
|
return false;
|
|
@@ -637,8 +949,8 @@ async function exists2(p) {
|
|
|
637
949
|
}
|
|
638
950
|
|
|
639
951
|
// src/adapters/platforms/claude.ts
|
|
640
|
-
import
|
|
641
|
-
import
|
|
952
|
+
import fs3 from "fs/promises";
|
|
953
|
+
import path5 from "path";
|
|
642
954
|
var ClaudeAdapter = class {
|
|
643
955
|
cwd;
|
|
644
956
|
constructor({ cwd }) {
|
|
@@ -648,8 +960,8 @@ var ClaudeAdapter = class {
|
|
|
648
960
|
* Transforms an OpenCode agent or skill into a Claude rule with completion contract.
|
|
649
961
|
*/
|
|
650
962
|
async transformToRule(filePath, type = "agent") {
|
|
651
|
-
const content = await
|
|
652
|
-
const fileName =
|
|
963
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
964
|
+
const fileName = path5.basename(filePath, ".md") === "SKILL" ? path5.basename(path5.dirname(filePath)) : path5.basename(filePath, ".md");
|
|
653
965
|
const ruleName = `${type}-${fileName.toLowerCase()}`;
|
|
654
966
|
const hardenedContent = `---
|
|
655
967
|
name: ${ruleName}
|
|
@@ -679,34 +991,34 @@ Every task completion MUST provide the following payload:
|
|
|
679
991
|
* Deploys agents, skills, and root CLAUDE.md.
|
|
680
992
|
*/
|
|
681
993
|
async deploy(installRoot = ".ai-workflow") {
|
|
682
|
-
const rulesDir =
|
|
683
|
-
await
|
|
684
|
-
const sourceAgentsDir =
|
|
685
|
-
const sourceSkillsDir =
|
|
686
|
-
const sourceCommandsDir =
|
|
687
|
-
const agentFiles = await
|
|
994
|
+
const rulesDir = path5.join(this.cwd, ".claude/rules");
|
|
995
|
+
await fs3.mkdir(rulesDir, { recursive: true });
|
|
996
|
+
const sourceAgentsDir = path5.join(this.cwd, installRoot, "opencode/agents");
|
|
997
|
+
const sourceSkillsDir = path5.join(this.cwd, installRoot, "opencode/skills");
|
|
998
|
+
const sourceCommandsDir = path5.join(this.cwd, installRoot, "opencode/commands");
|
|
999
|
+
const agentFiles = await fs3.readdir(sourceAgentsDir).catch(() => []);
|
|
688
1000
|
for (const file of agentFiles) {
|
|
689
1001
|
if (!file.endsWith(".md")) continue;
|
|
690
|
-
const transformed = await this.transformToRule(
|
|
691
|
-
await
|
|
1002
|
+
const transformed = await this.transformToRule(path5.join(sourceAgentsDir, file), "agent");
|
|
1003
|
+
await fs3.writeFile(path5.join(rulesDir, `${transformed.name}.md`), transformed.content);
|
|
692
1004
|
}
|
|
693
|
-
const skillFolders = await
|
|
1005
|
+
const skillFolders = await fs3.readdir(sourceSkillsDir).catch(() => []);
|
|
694
1006
|
for (const folder of skillFolders) {
|
|
695
|
-
const skillSource =
|
|
1007
|
+
const skillSource = path5.join(sourceSkillsDir, folder, "SKILL.md");
|
|
696
1008
|
if (await this.exists(skillSource)) {
|
|
697
1009
|
const transformed = await this.transformToRule(skillSource, "skill");
|
|
698
|
-
await
|
|
1010
|
+
await fs3.writeFile(path5.join(rulesDir, `${transformed.name}.md`), transformed.content);
|
|
699
1011
|
}
|
|
700
1012
|
}
|
|
701
1013
|
await this.deployRootClaudeMd(sourceCommandsDir);
|
|
702
1014
|
}
|
|
703
1015
|
async deployRootClaudeMd(sourceCommandsDir) {
|
|
704
|
-
const targetPath =
|
|
705
|
-
const commandFiles = await
|
|
1016
|
+
const targetPath = path5.join(this.cwd, "CLAUDE.md");
|
|
1017
|
+
const commandFiles = await fs3.readdir(sourceCommandsDir).catch(() => []);
|
|
706
1018
|
let commandsDoc = "";
|
|
707
1019
|
for (const file of commandFiles) {
|
|
708
1020
|
if (!file.endsWith(".md")) continue;
|
|
709
|
-
const name =
|
|
1021
|
+
const name = path5.basename(file, ".md");
|
|
710
1022
|
commandsDoc += `- \`ai-workflow run --spec-path=...\` (Target: ${name})
|
|
711
1023
|
`;
|
|
712
1024
|
}
|
|
@@ -730,11 +1042,11 @@ Adhere to the specific rules and personas defined in \`.claude/rules/\`.
|
|
|
730
1042
|
## Completion Contract
|
|
731
1043
|
Every task completion MUST follow the payload format defined in the individual rule files.
|
|
732
1044
|
`;
|
|
733
|
-
await
|
|
1045
|
+
await fs3.writeFile(targetPath, instructions);
|
|
734
1046
|
}
|
|
735
1047
|
async exists(p) {
|
|
736
1048
|
try {
|
|
737
|
-
await
|
|
1049
|
+
await fs3.access(p);
|
|
738
1050
|
return true;
|
|
739
1051
|
} catch {
|
|
740
1052
|
return false;
|
|
@@ -743,8 +1055,8 @@ Every task completion MUST follow the payload format defined in the individual r
|
|
|
743
1055
|
};
|
|
744
1056
|
|
|
745
1057
|
// src/adapters/platforms/codex.ts
|
|
746
|
-
import
|
|
747
|
-
import
|
|
1058
|
+
import fs4 from "fs/promises";
|
|
1059
|
+
import path6 from "path";
|
|
748
1060
|
var CodexAdapter = class {
|
|
749
1061
|
cwd;
|
|
750
1062
|
constructor({ cwd }) {
|
|
@@ -754,8 +1066,8 @@ var CodexAdapter = class {
|
|
|
754
1066
|
* Transforms an OpenCode agent into a Codex prompt with completion contract.
|
|
755
1067
|
*/
|
|
756
1068
|
async transformAgent(filePath) {
|
|
757
|
-
const content = await
|
|
758
|
-
const fileName =
|
|
1069
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
1070
|
+
const fileName = path6.basename(filePath, ".md");
|
|
759
1071
|
const hardenedContent = `${content}
|
|
760
1072
|
|
|
761
1073
|
## System Directives for Codex
|
|
@@ -787,55 +1099,55 @@ Every task completion MUST provide the following payload:
|
|
|
787
1099
|
* - Commands -> .codex/prompts/
|
|
788
1100
|
*/
|
|
789
1101
|
async deploy(installRoot = ".ai-workflow", force) {
|
|
790
|
-
const sourceAgentsDir =
|
|
791
|
-
const sourceSkillsDir =
|
|
792
|
-
const sourceCommandsDir =
|
|
793
|
-
const codexAgentsDir =
|
|
794
|
-
await
|
|
795
|
-
const agentFiles = await
|
|
1102
|
+
const sourceAgentsDir = path6.join(this.cwd, installRoot, "opencode/agents");
|
|
1103
|
+
const sourceSkillsDir = path6.join(this.cwd, installRoot, "opencode/skills");
|
|
1104
|
+
const sourceCommandsDir = path6.join(this.cwd, installRoot, "opencode/commands");
|
|
1105
|
+
const codexAgentsDir = path6.join(this.cwd, ".github/agents");
|
|
1106
|
+
await fs4.mkdir(codexAgentsDir, { recursive: true });
|
|
1107
|
+
const agentFiles = await fs4.readdir(sourceAgentsDir).catch(() => []);
|
|
796
1108
|
for (const file of agentFiles) {
|
|
797
1109
|
if (!file.endsWith(".md")) continue;
|
|
798
|
-
const transformed = await this.transformAgent(
|
|
799
|
-
await
|
|
1110
|
+
const transformed = await this.transformAgent(path6.join(sourceAgentsDir, file));
|
|
1111
|
+
await fs4.writeFile(path6.join(codexAgentsDir, `${transformed.name}.md`), transformed.content);
|
|
800
1112
|
}
|
|
801
|
-
const codexSkillsDir =
|
|
802
|
-
await
|
|
803
|
-
const skillFolders = await
|
|
1113
|
+
const codexSkillsDir = path6.join(this.cwd, ".codex/skills");
|
|
1114
|
+
await fs4.mkdir(codexSkillsDir, { recursive: true });
|
|
1115
|
+
const skillFolders = await fs4.readdir(sourceSkillsDir).catch(() => []);
|
|
804
1116
|
for (const folder of skillFolders) {
|
|
805
|
-
const skillSource =
|
|
1117
|
+
const skillSource = path6.join(sourceSkillsDir, folder, "SKILL.md");
|
|
806
1118
|
if (await this.exists(skillSource)) {
|
|
807
|
-
const targetDir =
|
|
808
|
-
await
|
|
809
|
-
const content = await
|
|
810
|
-
await
|
|
1119
|
+
const targetDir = path6.join(codexSkillsDir, folder);
|
|
1120
|
+
await fs4.mkdir(targetDir, { recursive: true });
|
|
1121
|
+
const content = await fs4.readFile(skillSource, "utf8");
|
|
1122
|
+
await fs4.writeFile(path6.join(targetDir, "SKILL.md"), content);
|
|
811
1123
|
}
|
|
812
1124
|
}
|
|
813
|
-
const codexPromptsDir =
|
|
814
|
-
await
|
|
815
|
-
const commandFiles = await
|
|
1125
|
+
const codexPromptsDir = path6.join(this.cwd, ".codex/prompts");
|
|
1126
|
+
await fs4.mkdir(codexPromptsDir, { recursive: true });
|
|
1127
|
+
const commandFiles = await fs4.readdir(sourceCommandsDir).catch(() => []);
|
|
816
1128
|
for (const file of commandFiles) {
|
|
817
1129
|
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
818
|
-
const targetFileName = file.endsWith(".toml") ? `${
|
|
819
|
-
const content = await
|
|
820
|
-
await
|
|
821
|
-
}
|
|
822
|
-
const codexPoliciesDir =
|
|
823
|
-
await
|
|
824
|
-
const sourcePoliciesDir =
|
|
825
|
-
const policyFiles = await
|
|
1130
|
+
const targetFileName = file.endsWith(".toml") ? `${path6.basename(file, ".toml")}.md` : file;
|
|
1131
|
+
const content = await fs4.readFile(path6.join(sourceCommandsDir, file), "utf8");
|
|
1132
|
+
await fs4.writeFile(path6.join(codexPromptsDir, targetFileName), content);
|
|
1133
|
+
}
|
|
1134
|
+
const codexPoliciesDir = path6.join(this.cwd, ".codex", "docs", "policies");
|
|
1135
|
+
await fs4.mkdir(codexPoliciesDir, { recursive: true });
|
|
1136
|
+
const sourcePoliciesDir = path6.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
1137
|
+
const policyFiles = await fs4.readdir(sourcePoliciesDir).catch(() => []);
|
|
826
1138
|
for (const file of policyFiles) {
|
|
827
1139
|
if (!file.endsWith(".md")) continue;
|
|
828
|
-
const policyContent = await
|
|
829
|
-
await
|
|
1140
|
+
const policyContent = await fs4.readFile(path6.join(sourcePoliciesDir, file), "utf8");
|
|
1141
|
+
await fs4.writeFile(path6.join(codexPoliciesDir, file), policyContent);
|
|
830
1142
|
}
|
|
831
1143
|
await this.deployRootCopilotInstructions();
|
|
832
1144
|
await this.deployRootCodexMd(force);
|
|
833
1145
|
await this.deployRootAgentsMd(force);
|
|
834
1146
|
}
|
|
835
1147
|
async deployRootCopilotInstructions() {
|
|
836
|
-
const githubDir =
|
|
837
|
-
await
|
|
838
|
-
const targetPath =
|
|
1148
|
+
const githubDir = path6.join(this.cwd, ".github");
|
|
1149
|
+
await fs4.mkdir(githubDir, { recursive: true });
|
|
1150
|
+
const targetPath = path6.join(githubDir, "copilot-instructions.md");
|
|
839
1151
|
const instructions = `# AI Workflow Kit Governance
|
|
840
1152
|
|
|
841
1153
|
This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
|
|
@@ -857,15 +1169,15 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
857
1169
|
## Completion Contract
|
|
858
1170
|
Every task completion MUST follow the payload format defined in the individual agent files.
|
|
859
1171
|
`;
|
|
860
|
-
await
|
|
1172
|
+
await fs4.writeFile(targetPath, instructions);
|
|
861
1173
|
}
|
|
862
1174
|
async deployRootCodexMd(force) {
|
|
863
|
-
const targetPath =
|
|
1175
|
+
const targetPath = path6.join(this.cwd, "CODEX.md");
|
|
864
1176
|
if (!force && await this.exists(targetPath)) return;
|
|
865
|
-
const templatePath =
|
|
1177
|
+
const templatePath = path6.join(this.cwd, ".ai-workflow", "templates", "CODEX.md.template");
|
|
866
1178
|
if (await this.exists(templatePath)) {
|
|
867
|
-
const content = await
|
|
868
|
-
await
|
|
1179
|
+
const content = await fs4.readFile(templatePath, "utf8");
|
|
1180
|
+
await fs4.writeFile(targetPath, content);
|
|
869
1181
|
return;
|
|
870
1182
|
}
|
|
871
1183
|
const instructions = `# AI Workflow Kit Governance
|
|
@@ -889,23 +1201,23 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
889
1201
|
## Completion Contract
|
|
890
1202
|
Every task completion MUST follow the payload format defined in the individual agent files.
|
|
891
1203
|
`;
|
|
892
|
-
await
|
|
1204
|
+
await fs4.writeFile(targetPath, instructions);
|
|
893
1205
|
}
|
|
894
1206
|
async deployRootAgentsMd(force) {
|
|
895
|
-
const targetPath =
|
|
1207
|
+
const targetPath = path6.join(this.cwd, "AGENTS.md");
|
|
896
1208
|
if (!force && await this.exists(targetPath)) return;
|
|
897
|
-
const templatePath =
|
|
1209
|
+
const templatePath = path6.join(this.cwd, ".ai-workflow", "AGENTS.md");
|
|
898
1210
|
if (await this.exists(templatePath)) {
|
|
899
|
-
let content = await
|
|
1211
|
+
let content = await fs4.readFile(templatePath, "utf8");
|
|
900
1212
|
content = content.replace(/\.agents\/skills/g, ".codex/skills");
|
|
901
1213
|
content = content.replace(/opencode\/commands/g, ".codex/prompts");
|
|
902
1214
|
content = content.replace(/opencode\/docs\/policies/g, ".codex/docs/policies");
|
|
903
|
-
await
|
|
1215
|
+
await fs4.writeFile(targetPath, content);
|
|
904
1216
|
}
|
|
905
1217
|
}
|
|
906
1218
|
async exists(p) {
|
|
907
1219
|
try {
|
|
908
|
-
await
|
|
1220
|
+
await fs4.access(p);
|
|
909
1221
|
return true;
|
|
910
1222
|
} catch {
|
|
911
1223
|
return false;
|
|
@@ -914,8 +1226,8 @@ Every task completion MUST follow the payload format defined in the individual a
|
|
|
914
1226
|
};
|
|
915
1227
|
|
|
916
1228
|
// src/adapters/platforms/antigravity.ts
|
|
917
|
-
import
|
|
918
|
-
import
|
|
1229
|
+
import fs5 from "fs/promises";
|
|
1230
|
+
import path7 from "path";
|
|
919
1231
|
import { pathToFileURL } from "url";
|
|
920
1232
|
var PERSONA_MAPPING = {
|
|
921
1233
|
"atlas": "Atlas",
|
|
@@ -938,8 +1250,8 @@ var AntigravityAdapter = class {
|
|
|
938
1250
|
* Transforms an OpenCode agent or specialized skill file to an Antigravity Skill.
|
|
939
1251
|
*/
|
|
940
1252
|
async transform(filePath) {
|
|
941
|
-
const content = await
|
|
942
|
-
const fileName =
|
|
1253
|
+
const content = await fs5.readFile(filePath, "utf8");
|
|
1254
|
+
const fileName = path7.basename(filePath, ".md") === "SKILL" ? path7.basename(path7.dirname(filePath)) : path7.basename(filePath, ".md");
|
|
943
1255
|
const persona = PERSONA_MAPPING[fileName.toLowerCase()] || "Astra";
|
|
944
1256
|
if (content.trim().startsWith("---")) {
|
|
945
1257
|
return { content, name: fileName };
|
|
@@ -975,17 +1287,17 @@ Every task completion MUST provide the following payload:
|
|
|
975
1287
|
* Also orchestrates the .antigravity/ native integration.
|
|
976
1288
|
*/
|
|
977
1289
|
async deploy(transformedItems, installRoot = ".ai-workflow") {
|
|
978
|
-
const agentsDir =
|
|
979
|
-
const skillsDir =
|
|
980
|
-
await
|
|
981
|
-
await
|
|
1290
|
+
const agentsDir = path7.join(this.cwd, installRoot, "opencode/agents");
|
|
1291
|
+
const skillsDir = path7.join(this.cwd, installRoot, "opencode/skills");
|
|
1292
|
+
await fs5.mkdir(agentsDir, { recursive: true });
|
|
1293
|
+
await fs5.mkdir(skillsDir, { recursive: true });
|
|
982
1294
|
for (const item of transformedItems) {
|
|
983
1295
|
const isAgent = PERSONA_MAPPING[item.name.toLowerCase()];
|
|
984
|
-
const targetDir = isAgent ? agentsDir :
|
|
1296
|
+
const targetDir = isAgent ? agentsDir : path7.join(skillsDir, item.name);
|
|
985
1297
|
const fileName = isAgent ? `${item.name}.md` : "SKILL.md";
|
|
986
1298
|
const absoluteTargetDir = isAgent ? agentsDir : targetDir;
|
|
987
|
-
await
|
|
988
|
-
await
|
|
1299
|
+
await fs5.mkdir(absoluteTargetDir, { recursive: true });
|
|
1300
|
+
await fs5.writeFile(path7.join(absoluteTargetDir, fileName), item.content);
|
|
989
1301
|
}
|
|
990
1302
|
await this.deployNativeExtensions(installRoot);
|
|
991
1303
|
await this.deployInstructions();
|
|
@@ -995,13 +1307,13 @@ Every task completion MUST provide the following payload:
|
|
|
995
1307
|
* Using hard copies instead of symlinks ensures native discovery across all platforms.
|
|
996
1308
|
*/
|
|
997
1309
|
async deployNativeExtensions(installRoot = ".ai-workflow") {
|
|
998
|
-
const antigravityDir =
|
|
999
|
-
const antigravityAgents =
|
|
1000
|
-
const antigravitySkills =
|
|
1001
|
-
const antigravityCommands =
|
|
1002
|
-
await
|
|
1003
|
-
await
|
|
1004
|
-
await
|
|
1310
|
+
const antigravityDir = path7.join(this.cwd, ".agents");
|
|
1311
|
+
const antigravityAgents = path7.join(antigravityDir, "agents");
|
|
1312
|
+
const antigravitySkills = path7.join(antigravityDir, "skills");
|
|
1313
|
+
const antigravityCommands = path7.join(antigravityDir, "commands");
|
|
1314
|
+
await fs5.mkdir(antigravityAgents, { recursive: true });
|
|
1315
|
+
await fs5.mkdir(antigravitySkills, { recursive: true });
|
|
1316
|
+
await fs5.mkdir(antigravityCommands, { recursive: true });
|
|
1005
1317
|
const getUrls = (targetPath) => {
|
|
1006
1318
|
const linuxUrl = pathToFileURL(targetPath).href;
|
|
1007
1319
|
const urls = [linuxUrl];
|
|
@@ -1043,8 +1355,8 @@ Every task completion MUST provide the following payload:
|
|
|
1043
1355
|
"user_rules"
|
|
1044
1356
|
]
|
|
1045
1357
|
};
|
|
1046
|
-
const sourceAgentsDir =
|
|
1047
|
-
const agentFiles = await
|
|
1358
|
+
const sourceAgentsDir = path7.join(this.cwd, installRoot, "opencode/agents");
|
|
1359
|
+
const agentFiles = await fs5.readdir(sourceAgentsDir).catch(() => []);
|
|
1048
1360
|
const AGENTS_INFO = {
|
|
1049
1361
|
"atlas": { name: "Atlas", description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
1050
1362
|
"nexus": { name: "Nexus", description: "Discovery, requirement, scope, and specification owner" },
|
|
@@ -1055,14 +1367,14 @@ Every task completion MUST provide the following payload:
|
|
|
1055
1367
|
};
|
|
1056
1368
|
for (const file of agentFiles) {
|
|
1057
1369
|
if (!file.endsWith(".md")) continue;
|
|
1058
|
-
const lowercaseName =
|
|
1370
|
+
const lowercaseName = path7.basename(file, ".md").toLowerCase();
|
|
1059
1371
|
const info = AGENTS_INFO[lowercaseName];
|
|
1060
1372
|
if (!info) continue;
|
|
1061
|
-
const agentFolder =
|
|
1062
|
-
await
|
|
1063
|
-
const content = await
|
|
1064
|
-
const mdTarget =
|
|
1065
|
-
await
|
|
1373
|
+
const agentFolder = path7.join(antigravityAgents, lowercaseName);
|
|
1374
|
+
await fs5.mkdir(agentFolder, { recursive: true });
|
|
1375
|
+
const content = await fs5.readFile(path7.join(sourceAgentsDir, file), "utf8");
|
|
1376
|
+
const mdTarget = path7.join(agentFolder, `${lowercaseName}.md`);
|
|
1377
|
+
await fs5.writeFile(mdTarget, content);
|
|
1066
1378
|
const agentJson = {
|
|
1067
1379
|
name: lowercaseName,
|
|
1068
1380
|
description: info.description,
|
|
@@ -1083,7 +1395,7 @@ ${getUrls(mdTarget)}`
|
|
|
1083
1395
|
}
|
|
1084
1396
|
}
|
|
1085
1397
|
};
|
|
1086
|
-
await
|
|
1398
|
+
await fs5.writeFile(path7.join(agentFolder, "agent.json"), JSON.stringify(agentJson, null, 2));
|
|
1087
1399
|
}
|
|
1088
1400
|
const SUBAGENTS_INFO = {
|
|
1089
1401
|
"Architecture-Specialist": { description: "Select the simplest architecture that safely satisfies current requirements", skill: "architecture" },
|
|
@@ -1108,9 +1420,9 @@ ${getUrls(mdTarget)}`
|
|
|
1108
1420
|
};
|
|
1109
1421
|
for (const [name, subInfo] of Object.entries(SUBAGENTS_INFO)) {
|
|
1110
1422
|
const lowercaseSubagentName = name.toLowerCase();
|
|
1111
|
-
const subagentFolder =
|
|
1112
|
-
await
|
|
1113
|
-
const skillPath =
|
|
1423
|
+
const subagentFolder = path7.join(antigravityAgents, lowercaseSubagentName);
|
|
1424
|
+
await fs5.mkdir(subagentFolder, { recursive: true });
|
|
1425
|
+
const skillPath = path7.join(antigravitySkills, subInfo.skill, "SKILL.md");
|
|
1114
1426
|
const subagentJson = {
|
|
1115
1427
|
name: lowercaseSubagentName,
|
|
1116
1428
|
description: subInfo.description,
|
|
@@ -1131,50 +1443,50 @@ ${getUrls(skillPath)}`
|
|
|
1131
1443
|
}
|
|
1132
1444
|
}
|
|
1133
1445
|
};
|
|
1134
|
-
await
|
|
1446
|
+
await fs5.writeFile(path7.join(subagentFolder, "agent.json"), JSON.stringify(subagentJson, null, 2));
|
|
1135
1447
|
}
|
|
1136
|
-
const sourceSkillsDir =
|
|
1137
|
-
const skillFolders = await
|
|
1448
|
+
const sourceSkillsDir = path7.join(this.cwd, installRoot, "opencode/skills");
|
|
1449
|
+
const skillFolders = await fs5.readdir(sourceSkillsDir).catch(() => []);
|
|
1138
1450
|
for (const folder of skillFolders) {
|
|
1139
|
-
const skillSource =
|
|
1451
|
+
const skillSource = path7.join(sourceSkillsDir, folder, "SKILL.md");
|
|
1140
1452
|
if (await this.exists(skillSource)) {
|
|
1141
|
-
await
|
|
1142
|
-
const content = await
|
|
1143
|
-
await
|
|
1453
|
+
await fs5.mkdir(path7.join(antigravitySkills, folder), { recursive: true });
|
|
1454
|
+
const content = await fs5.readFile(skillSource, "utf8");
|
|
1455
|
+
await fs5.writeFile(path7.join(antigravitySkills, folder, "SKILL.md"), content);
|
|
1144
1456
|
}
|
|
1145
1457
|
}
|
|
1146
|
-
const sourceCommandsDir =
|
|
1458
|
+
const sourceCommandsDir = path7.join(this.cwd, installRoot, "opencode/commands");
|
|
1147
1459
|
if (await this.exists(sourceCommandsDir)) {
|
|
1148
|
-
const commandFiles = await
|
|
1460
|
+
const commandFiles = await fs5.readdir(sourceCommandsDir).catch(() => []);
|
|
1149
1461
|
for (const file of commandFiles) {
|
|
1150
1462
|
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
1151
|
-
const content = await
|
|
1152
|
-
await
|
|
1463
|
+
const content = await fs5.readFile(path7.join(sourceCommandsDir, file), "utf8");
|
|
1464
|
+
await fs5.writeFile(path7.join(antigravityCommands, file), content);
|
|
1153
1465
|
}
|
|
1154
1466
|
}
|
|
1155
|
-
const sourcePoliciesDir =
|
|
1156
|
-
const antigravityPolicies =
|
|
1467
|
+
const sourcePoliciesDir = path7.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
1468
|
+
const antigravityPolicies = path7.join(antigravityDir, "docs", "policies");
|
|
1157
1469
|
if (await this.exists(sourcePoliciesDir)) {
|
|
1158
|
-
await
|
|
1159
|
-
const policyFiles = await
|
|
1470
|
+
await fs5.mkdir(antigravityPolicies, { recursive: true });
|
|
1471
|
+
const policyFiles = await fs5.readdir(sourcePoliciesDir).catch(() => []);
|
|
1160
1472
|
for (const file of policyFiles) {
|
|
1161
1473
|
if (!file.endsWith(".md")) continue;
|
|
1162
|
-
const content = await
|
|
1163
|
-
await
|
|
1474
|
+
const content = await fs5.readFile(path7.join(sourcePoliciesDir, file), "utf8");
|
|
1475
|
+
await fs5.writeFile(path7.join(antigravityPolicies, file), content);
|
|
1164
1476
|
}
|
|
1165
1477
|
}
|
|
1166
|
-
const settingsPath =
|
|
1478
|
+
const settingsPath = path7.join(antigravityDir, "settings.json");
|
|
1167
1479
|
if (!await this.exists(settingsPath)) {
|
|
1168
1480
|
const settings = {
|
|
1169
1481
|
model: { name: "auto" },
|
|
1170
1482
|
ui: { theme: "Ayu" }
|
|
1171
1483
|
};
|
|
1172
|
-
await
|
|
1484
|
+
await fs5.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
1173
1485
|
}
|
|
1174
1486
|
}
|
|
1175
1487
|
async exists(p) {
|
|
1176
1488
|
try {
|
|
1177
|
-
await
|
|
1489
|
+
await fs5.access(p);
|
|
1178
1490
|
return true;
|
|
1179
1491
|
} catch {
|
|
1180
1492
|
return false;
|
|
@@ -1184,10 +1496,10 @@ ${getUrls(skillPath)}`
|
|
|
1184
1496
|
* Deploys the main ANTIGRAVITY.md and .antigravityignore instructions to the project root.
|
|
1185
1497
|
*/
|
|
1186
1498
|
async deployInstructions() {
|
|
1187
|
-
const targetPath =
|
|
1188
|
-
const ignorePath =
|
|
1499
|
+
const targetPath = path7.join(this.cwd, "ANTIGRAVITY.md");
|
|
1500
|
+
const ignorePath = path7.join(this.cwd, ".antigravityignore");
|
|
1189
1501
|
try {
|
|
1190
|
-
await
|
|
1502
|
+
await fs5.access(targetPath);
|
|
1191
1503
|
} catch {
|
|
1192
1504
|
const instructions = `# AI Workflow Kit - Engineering Governance
|
|
1193
1505
|
|
|
@@ -1216,10 +1528,10 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
1216
1528
|
- **Native Discovery**: This project uses workspace-specific extensions in \`.agents/\`.
|
|
1217
1529
|
- **Workflow Entry**: Use \`/atlas\` to begin any task.
|
|
1218
1530
|
`;
|
|
1219
|
-
await
|
|
1531
|
+
await fs5.writeFile(targetPath, instructions);
|
|
1220
1532
|
}
|
|
1221
1533
|
try {
|
|
1222
|
-
await
|
|
1534
|
+
await fs5.access(ignorePath);
|
|
1223
1535
|
} catch {
|
|
1224
1536
|
const ignoreContent = `.ai-workflow/
|
|
1225
1537
|
.ai-workflow-backups/
|
|
@@ -1232,7 +1544,7 @@ EVIDENCE.json
|
|
|
1232
1544
|
.agents/tmp/
|
|
1233
1545
|
.agents/history/
|
|
1234
1546
|
`;
|
|
1235
|
-
await
|
|
1547
|
+
await fs5.writeFile(ignorePath, ignoreContent);
|
|
1236
1548
|
}
|
|
1237
1549
|
}
|
|
1238
1550
|
};
|
|
@@ -1266,7 +1578,7 @@ function buildGitignoreBlock(entries) {
|
|
|
1266
1578
|
].join("\n");
|
|
1267
1579
|
}
|
|
1268
1580
|
async function upsertGitignoreBlock(cwd, entries) {
|
|
1269
|
-
const gitignorePath =
|
|
1581
|
+
const gitignorePath = path8.join(cwd, ".gitignore");
|
|
1270
1582
|
const block = buildGitignoreBlock(entries);
|
|
1271
1583
|
const beginMarker = "# BEGIN AI WORKFLOW KIT";
|
|
1272
1584
|
const endMarker = "# END AI WORKFLOW KIT";
|
|
@@ -1276,7 +1588,7 @@ async function upsertGitignoreBlock(cwd, entries) {
|
|
|
1276
1588
|
`);
|
|
1277
1589
|
return "created";
|
|
1278
1590
|
}
|
|
1279
|
-
const current = await
|
|
1591
|
+
const current = await fs6.readFile(gitignorePath, "utf8");
|
|
1280
1592
|
const beginIndex = current.indexOf(beginMarker);
|
|
1281
1593
|
const endIndex = current.indexOf(endMarker);
|
|
1282
1594
|
if (beginIndex >= 0 && endIndex > beginIndex) {
|
|
@@ -1297,7 +1609,7 @@ async function upsertGitignoreBlock(cwd, entries) {
|
|
|
1297
1609
|
return "updated";
|
|
1298
1610
|
}
|
|
1299
1611
|
async function injectScripts(cwd) {
|
|
1300
|
-
const pkgPath =
|
|
1612
|
+
const pkgPath = path8.join(cwd, "package.json");
|
|
1301
1613
|
if (!await exists(pkgPath)) return false;
|
|
1302
1614
|
try {
|
|
1303
1615
|
const pkg = await readJson(pkgPath);
|
|
@@ -1315,7 +1627,7 @@ async function injectScripts(cwd) {
|
|
|
1315
1627
|
async function verifySelfExecutionGuard(cwd, devMode) {
|
|
1316
1628
|
if (!devMode) {
|
|
1317
1629
|
try {
|
|
1318
|
-
const pkgPath =
|
|
1630
|
+
const pkgPath = path8.join(cwd, "package.json");
|
|
1319
1631
|
if (await exists(pkgPath)) {
|
|
1320
1632
|
const pkg = await readJson(pkgPath);
|
|
1321
1633
|
if (pkg.name === "@williambeto/ai-workflow") {
|
|
@@ -1363,8 +1675,8 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1363
1675
|
const linkBackups = [];
|
|
1364
1676
|
const linkCreated = [];
|
|
1365
1677
|
for (const { linkPath, targetPath } of linkEntries) {
|
|
1366
|
-
const absoluteLinkPath =
|
|
1367
|
-
const absoluteTargetPath =
|
|
1678
|
+
const absoluteLinkPath = path8.join(cwd, linkPath);
|
|
1679
|
+
const absoluteTargetPath = path8.join(cwd, targetPath);
|
|
1368
1680
|
if (!await exists(absoluteTargetPath)) {
|
|
1369
1681
|
throw new Error(`missing link target: ${targetPath}`);
|
|
1370
1682
|
}
|
|
@@ -1383,14 +1695,14 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1383
1695
|
maxPerFile: 20
|
|
1384
1696
|
});
|
|
1385
1697
|
linkBackups.push(backupPath);
|
|
1386
|
-
await
|
|
1698
|
+
await fs6.rm(absoluteLinkPath, { recursive: true, force: true });
|
|
1387
1699
|
}
|
|
1388
|
-
const stat = await
|
|
1700
|
+
const stat = await fs6.lstat(absoluteTargetPath);
|
|
1389
1701
|
const type = process.platform === "win32" ? stat.isDirectory() ? "junction" : "file" : stat.isDirectory() ? "dir" : "file";
|
|
1390
|
-
const linkTarget = process.platform === "win32" ? absoluteTargetPath :
|
|
1702
|
+
const linkTarget = process.platform === "win32" ? absoluteTargetPath : path8.relative(path8.dirname(absoluteLinkPath), absoluteTargetPath);
|
|
1391
1703
|
try {
|
|
1392
|
-
await
|
|
1393
|
-
await
|
|
1704
|
+
await fs6.mkdir(path8.dirname(absoluteLinkPath), { recursive: true });
|
|
1705
|
+
await fs6.symlink(linkTarget, absoluteLinkPath, type);
|
|
1394
1706
|
} catch (error) {
|
|
1395
1707
|
throw new Error(
|
|
1396
1708
|
`failed to create symlink '${linkPath}' -> '${targetPath}' (${error.message}). For Windows, enable Developer Mode or run terminal as Administrator. For Linux/macOS, verify write permissions in project root.`
|
|
@@ -1403,9 +1715,9 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1403
1715
|
async function executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity, force) {
|
|
1404
1716
|
if (platforms.length === 0) return;
|
|
1405
1717
|
console.log("ai-workflow: migrating to native platforms...");
|
|
1406
|
-
const agentsDir =
|
|
1407
|
-
const skillsDir =
|
|
1408
|
-
const agentFiles = (await
|
|
1718
|
+
const agentsDir = path8.join(cwd, installRoot, "opencode/agents");
|
|
1719
|
+
const skillsDir = path8.join(cwd, installRoot, "opencode/skills");
|
|
1720
|
+
const agentFiles = (await fs6.readdir(agentsDir).catch(() => [])).filter((f) => f.endsWith(".md")).map((f) => path8.join(agentsDir, f));
|
|
1409
1721
|
if (claude) {
|
|
1410
1722
|
const adapter = new ClaudeAdapter({ cwd });
|
|
1411
1723
|
await adapter.deploy(installRoot);
|
|
@@ -1418,10 +1730,10 @@ async function executePlatformMigrations(cwd, installRoot, platforms, claude, co
|
|
|
1418
1730
|
}
|
|
1419
1731
|
if (antigravity) {
|
|
1420
1732
|
const adapter = new AntigravityAdapter({ cwd });
|
|
1421
|
-
const skillFolders = await
|
|
1733
|
+
const skillFolders = await fs6.readdir(skillsDir).catch(() => []);
|
|
1422
1734
|
const skillFiles = [];
|
|
1423
1735
|
for (const folder of skillFolders) {
|
|
1424
|
-
const skillPath =
|
|
1736
|
+
const skillPath = path8.join(skillsDir, folder, "SKILL.md");
|
|
1425
1737
|
if (await exists(skillPath)) {
|
|
1426
1738
|
skillFiles.push(skillPath);
|
|
1427
1739
|
}
|
|
@@ -1553,15 +1865,15 @@ async function runInit({
|
|
|
1553
1865
|
}
|
|
1554
1866
|
|
|
1555
1867
|
// src/cli/commands/doctor.ts
|
|
1556
|
-
import
|
|
1557
|
-
import
|
|
1868
|
+
import fs7 from "fs/promises";
|
|
1869
|
+
import path9 from "path";
|
|
1558
1870
|
var REQUIRED_FILES = [
|
|
1559
1871
|
".ai-workflow",
|
|
1560
1872
|
"opencode/README.md"
|
|
1561
1873
|
];
|
|
1562
1874
|
async function isSymlink(filePath) {
|
|
1563
1875
|
try {
|
|
1564
|
-
const stat = await
|
|
1876
|
+
const stat = await fs7.lstat(filePath);
|
|
1565
1877
|
return stat.isSymbolicLink();
|
|
1566
1878
|
} catch {
|
|
1567
1879
|
return false;
|
|
@@ -1587,7 +1899,7 @@ async function checkOpenCodeRuntime(cwd, state) {
|
|
|
1587
1899
|
}
|
|
1588
1900
|
async function checkRequiredFiles(cwd, state) {
|
|
1589
1901
|
for (const relativePath of REQUIRED_FILES) {
|
|
1590
|
-
const ok = await exists(
|
|
1902
|
+
const ok = await exists(path9.join(cwd, relativePath));
|
|
1591
1903
|
if (ok) {
|
|
1592
1904
|
console.log(`PASS ${relativePath}`);
|
|
1593
1905
|
} else {
|
|
@@ -1597,8 +1909,8 @@ async function checkRequiredFiles(cwd, state) {
|
|
|
1597
1909
|
}
|
|
1598
1910
|
}
|
|
1599
1911
|
async function checkAiWorkflowConfig(cwd, state) {
|
|
1600
|
-
const configPathV2 =
|
|
1601
|
-
const configPathLegacy =
|
|
1912
|
+
const configPathV2 = path9.join(cwd, ".ai-workflow/config.json");
|
|
1913
|
+
const configPathLegacy = path9.join(cwd, ".ai-workflow.json");
|
|
1602
1914
|
let configPath = null;
|
|
1603
1915
|
if (await exists(configPathV2)) {
|
|
1604
1916
|
configPath = configPathV2;
|
|
@@ -1608,7 +1920,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1608
1920
|
if (!configPath) return;
|
|
1609
1921
|
try {
|
|
1610
1922
|
const config = await readJson(configPath);
|
|
1611
|
-
const basename =
|
|
1923
|
+
const basename = path9.basename(configPath);
|
|
1612
1924
|
if (config.installMode === "project-local" || config.mode === "standalone") {
|
|
1613
1925
|
console.log("PASS install mode is project-local/standalone");
|
|
1614
1926
|
} else {
|
|
@@ -1635,7 +1947,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1635
1947
|
}
|
|
1636
1948
|
if (Array.isArray(config.managedFiles)) {
|
|
1637
1949
|
for (const relativePath of config.managedFiles) {
|
|
1638
|
-
const fileExists = await exists(
|
|
1950
|
+
const fileExists = await exists(path9.join(cwd, relativePath));
|
|
1639
1951
|
if (!fileExists) {
|
|
1640
1952
|
state.hasWarning = true;
|
|
1641
1953
|
console.log(`WARN managed file missing: ${relativePath}`);
|
|
@@ -1644,7 +1956,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1644
1956
|
}
|
|
1645
1957
|
if (Array.isArray(config.managedLinks)) {
|
|
1646
1958
|
for (const relativePath of config.managedLinks) {
|
|
1647
|
-
const absolutePath =
|
|
1959
|
+
const absolutePath = path9.join(cwd, relativePath);
|
|
1648
1960
|
if (!await exists(absolutePath)) {
|
|
1649
1961
|
state.hasWarning = true;
|
|
1650
1962
|
console.log(`WARN managed link missing: ${relativePath}`);
|
|
@@ -1658,11 +1970,11 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1658
1970
|
}
|
|
1659
1971
|
} catch {
|
|
1660
1972
|
state.hasFailure = true;
|
|
1661
|
-
console.log(`FAIL ${
|
|
1973
|
+
console.log(`FAIL ${path9.basename(configPath)} is not valid JSON`);
|
|
1662
1974
|
}
|
|
1663
1975
|
}
|
|
1664
1976
|
async function checkOpencodeJson(cwd, state) {
|
|
1665
|
-
const opencodePath =
|
|
1977
|
+
const opencodePath = path9.join(cwd, "opencode.jsonc");
|
|
1666
1978
|
if (!await exists(opencodePath)) {
|
|
1667
1979
|
state.hasFailure = true;
|
|
1668
1980
|
console.log("FAIL opencode.jsonc missing");
|
|
@@ -1708,7 +2020,7 @@ async function checkOpencodeJson(cwd, state) {
|
|
|
1708
2020
|
}
|
|
1709
2021
|
}
|
|
1710
2022
|
async function checkPackageJson(cwd, state) {
|
|
1711
|
-
const packageJsonPath =
|
|
2023
|
+
const packageJsonPath = path9.join(cwd, "package.json");
|
|
1712
2024
|
if (await exists(packageJsonPath)) {
|
|
1713
2025
|
try {
|
|
1714
2026
|
const packageJson = await readJson(packageJsonPath);
|
|
@@ -1731,7 +2043,7 @@ async function checkCodexRuntime(cwd, state) {
|
|
|
1731
2043
|
{ name: ".codex policies detected", path: ".codex/docs/policies" }
|
|
1732
2044
|
];
|
|
1733
2045
|
for (const check of checks) {
|
|
1734
|
-
const ok = await exists(
|
|
2046
|
+
const ok = await exists(path9.join(cwd, check.path));
|
|
1735
2047
|
if (ok) {
|
|
1736
2048
|
console.log(`PASS ${check.name}`);
|
|
1737
2049
|
} else {
|
|
@@ -1739,7 +2051,7 @@ async function checkCodexRuntime(cwd, state) {
|
|
|
1739
2051
|
console.log(`FAIL ${check.name}`);
|
|
1740
2052
|
}
|
|
1741
2053
|
}
|
|
1742
|
-
const codexMdOk = await exists(
|
|
2054
|
+
const codexMdOk = await exists(path9.join(cwd, "CODEX.md"));
|
|
1743
2055
|
if (codexMdOk) {
|
|
1744
2056
|
console.log(`PASS root CODEX.md exists`);
|
|
1745
2057
|
}
|
|
@@ -1788,7 +2100,7 @@ function buildDeliverySummary({ evidence }) {
|
|
|
1788
2100
|
return {
|
|
1789
2101
|
Status: evidence?.status || "BLOCKED",
|
|
1790
2102
|
Branch: evidence?.branch || "unknown",
|
|
1791
|
-
Changes: (evidence?.changedFiles || []).join(", ") || "No changed files recorded",
|
|
2103
|
+
Changes: (evidence?.deliveryChangedFiles || evidence?.changedFiles || []).join(", ") || "No changed files recorded",
|
|
1792
2104
|
Validation: commands,
|
|
1793
2105
|
"Known limitations": (evidence?.limitations || []).join("; ") || "None recorded"
|
|
1794
2106
|
};
|
|
@@ -1822,12 +2134,41 @@ async function runCollectEvidence({
|
|
|
1822
2134
|
userRequest = null,
|
|
1823
2135
|
explicitApprovals = [],
|
|
1824
2136
|
visualDist = null,
|
|
1825
|
-
port = null
|
|
2137
|
+
port = null,
|
|
2138
|
+
workflowEvidence = null,
|
|
2139
|
+
claimedOutcome = null,
|
|
2140
|
+
deliveryChangedFiles = null,
|
|
2141
|
+
deliveryAddedFiles = null,
|
|
2142
|
+
deliveryDeletedFiles = null
|
|
1826
2143
|
}) {
|
|
2144
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
2145
|
+
console.log("[FINALIZATION DEFERRED] Parent ai-workflow execute process owns canonical finalization.");
|
|
2146
|
+
return { status: "DEFERRED_TO_ORCHESTRATOR", internalStatus: "DEFERRED", commands: [] };
|
|
2147
|
+
}
|
|
1827
2148
|
const policies = resolvePoliciesFromLegacyMode(mode, evidencePolicy);
|
|
1828
|
-
const
|
|
2149
|
+
const workspaceGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
|
|
2150
|
+
const observedDeliveryFiles = deliveryChangedFiles ?? workspaceGuard.getChangedFiles();
|
|
2151
|
+
const qualityGuard = new QualityGuard({
|
|
2152
|
+
cwd,
|
|
2153
|
+
taskSlug,
|
|
2154
|
+
evidencePolicy: policies.evidencePolicy,
|
|
2155
|
+
riskLevel: policies.riskLevel,
|
|
2156
|
+
scopedChangedFiles: observedDeliveryFiles
|
|
2157
|
+
});
|
|
1829
2158
|
const planner = new ValidationPlanner({ cwd, qualityGuard });
|
|
1830
|
-
const tasks = await planner.plan(profile
|
|
2159
|
+
const tasks = await planner.plan(profile, {
|
|
2160
|
+
requireObservedValidation: claimedOutcome === "ALREADY_SATISFIED" && observedDeliveryFiles.length === 0
|
|
2161
|
+
});
|
|
2162
|
+
const docs = observedDeliveryFiles.filter((file) => /(^|\/)(docs?|readme|changelog)|\.md$/i.test(file));
|
|
2163
|
+
if (tasks.length === 0 && docs.length > 0 && docs.length === observedDeliveryFiles.length) {
|
|
2164
|
+
tasks.push({
|
|
2165
|
+
name: "docs-integrity",
|
|
2166
|
+
command: ["aiwk", "internal", "docs-integrity"],
|
|
2167
|
+
kind: "lint",
|
|
2168
|
+
internal: "document-integrity",
|
|
2169
|
+
files: docs
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
1831
2172
|
const persist = !dryRun && policies.evidencePolicy === "required";
|
|
1832
2173
|
const collector = new EvidenceCollector({
|
|
1833
2174
|
cwd,
|
|
@@ -1839,7 +2180,13 @@ async function runCollectEvidence({
|
|
|
1839
2180
|
userRequest,
|
|
1840
2181
|
explicitApprovals,
|
|
1841
2182
|
visualDist,
|
|
1842
|
-
port: port || 8080
|
|
2183
|
+
port: port || 8080,
|
|
2184
|
+
workflowEvidence,
|
|
2185
|
+
claimedOutcome,
|
|
2186
|
+
deliveryChangedFiles: observedDeliveryFiles,
|
|
2187
|
+
deliveryAddedFiles,
|
|
2188
|
+
deliveryDeletedFiles,
|
|
2189
|
+
riskLevel: policies.riskLevel
|
|
1843
2190
|
});
|
|
1844
2191
|
const evidence = await collector.collect(tasks, { writeArtifact: persist });
|
|
1845
2192
|
const summary = buildDeliverySummary({ evidence });
|
|
@@ -1854,11 +2201,11 @@ async function runCollectEvidence({
|
|
|
1854
2201
|
|
|
1855
2202
|
// src/core/healing/cli-remediation-executor.ts
|
|
1856
2203
|
import { spawnSync, execSync as execSync2 } from "child_process";
|
|
1857
|
-
import
|
|
1858
|
-
import
|
|
2204
|
+
import fs8 from "fs/promises";
|
|
2205
|
+
import path10 from "path";
|
|
1859
2206
|
async function readScripts(cwd) {
|
|
1860
2207
|
try {
|
|
1861
|
-
const pkg = JSON.parse(await
|
|
2208
|
+
const pkg = JSON.parse(await fs8.readFile(path10.join(cwd, "package.json"), "utf8"));
|
|
1862
2209
|
return pkg.scripts || {};
|
|
1863
2210
|
} catch {
|
|
1864
2211
|
return {};
|
|
@@ -1948,8 +2295,170 @@ function createCliRemediationExecutor(cwd) {
|
|
|
1948
2295
|
}
|
|
1949
2296
|
|
|
1950
2297
|
// src/cli/commands/run.ts
|
|
1951
|
-
import
|
|
2298
|
+
import path14 from "path";
|
|
2299
|
+
import fs12 from "fs/promises";
|
|
2300
|
+
|
|
2301
|
+
// src/core/sdd/approval-receipt.ts
|
|
2302
|
+
var import_ajv = __toESM(require_ajv(), 1);
|
|
2303
|
+
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
1952
2304
|
import fs10 from "fs/promises";
|
|
2305
|
+
import path12 from "path";
|
|
2306
|
+
|
|
2307
|
+
// src/core/sdd/workflow-artifacts.ts
|
|
2308
|
+
import { createHash } from "crypto";
|
|
2309
|
+
import fs9 from "fs/promises";
|
|
2310
|
+
import path11 from "path";
|
|
2311
|
+
function sha256(content) {
|
|
2312
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2313
|
+
}
|
|
2314
|
+
async function hashFile(filePath) {
|
|
2315
|
+
return sha256(await fs9.readFile(filePath));
|
|
2316
|
+
}
|
|
2317
|
+
async function writeFileAtomic(filePath, content) {
|
|
2318
|
+
await fs9.mkdir(path11.dirname(filePath), { recursive: true });
|
|
2319
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
2320
|
+
try {
|
|
2321
|
+
await fs9.writeFile(temporaryPath, content, { encoding: "utf8", flag: "wx" });
|
|
2322
|
+
await fs9.rename(temporaryPath, filePath);
|
|
2323
|
+
} catch (error) {
|
|
2324
|
+
await fs9.rm(temporaryPath, { force: true }).catch(() => {
|
|
2325
|
+
});
|
|
2326
|
+
throw error;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
function stripMarkdownFence(content) {
|
|
2330
|
+
const trimmed = content.trim();
|
|
2331
|
+
const match = trimmed.match(/^```(?:markdown|md|json)?\s*\n([\s\S]*?)\n```$/i);
|
|
2332
|
+
return (match ? match[1] : trimmed).trim() + "\n";
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/core/sdd/approval-receipt.ts
|
|
2336
|
+
var ApprovalReceiptValidator = class {
|
|
2337
|
+
cwd;
|
|
2338
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
2339
|
+
this.cwd = path12.resolve(cwd);
|
|
2340
|
+
}
|
|
2341
|
+
async validate(receiptPath, specPath, expectedWorkflowId) {
|
|
2342
|
+
const absoluteReceiptPath = path12.resolve(this.cwd, receiptPath);
|
|
2343
|
+
if (absoluteReceiptPath !== this.cwd && !absoluteReceiptPath.startsWith(`${this.cwd}${path12.sep}`)) {
|
|
2344
|
+
return { valid: false, reason: "Approval receipt must be inside the repository." };
|
|
2345
|
+
}
|
|
2346
|
+
try {
|
|
2347
|
+
const stat = await fs10.lstat(absoluteReceiptPath);
|
|
2348
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
2349
|
+
return { valid: false, reason: "Approval receipt must be a regular, non-symlink file." };
|
|
2350
|
+
}
|
|
2351
|
+
} catch {
|
|
2352
|
+
return { valid: false, reason: `Approval receipt not found: ${receiptPath}` };
|
|
2353
|
+
}
|
|
2354
|
+
let receipt;
|
|
2355
|
+
try {
|
|
2356
|
+
receipt = JSON.parse(await fs10.readFile(absoluteReceiptPath, "utf8"));
|
|
2357
|
+
const root = getPackageRoot();
|
|
2358
|
+
const schema = JSON.parse(await fs10.readFile(path12.join(root, "dist-assets/schemas/approval-receipt.schema.json"), "utf8"));
|
|
2359
|
+
const ajv = new import_ajv.default({ allErrors: true });
|
|
2360
|
+
(0, import_ajv_formats.default)(ajv);
|
|
2361
|
+
const validateSchema = ajv.compile(schema);
|
|
2362
|
+
if (!validateSchema(receipt)) {
|
|
2363
|
+
const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
|
|
2364
|
+
return { valid: false, reason: `Approval receipt schema violation: ${errors}` };
|
|
2365
|
+
}
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
return { valid: false, reason: `Approval receipt validation failed: ${error.message}` };
|
|
2368
|
+
}
|
|
2369
|
+
if (expectedWorkflowId && receipt.workflowId !== expectedWorkflowId) {
|
|
2370
|
+
return { valid: false, reason: `Approval receipt workflowId '${receipt.workflowId}' does not match '${expectedWorkflowId}'.` };
|
|
2371
|
+
}
|
|
2372
|
+
const approvedSpecHash = await hashFile(specPath);
|
|
2373
|
+
if (receipt.approvedSpecHash !== approvedSpecHash) {
|
|
2374
|
+
return { valid: false, reason: "Approval receipt hash does not match the approved specification bytes." };
|
|
2375
|
+
}
|
|
2376
|
+
return { valid: true, receipt, approvedSpecHash };
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
|
|
2380
|
+
// src/core/sdd/technical-plan-validator.ts
|
|
2381
|
+
var import_ajv2 = __toESM(require_ajv(), 1);
|
|
2382
|
+
import fs11 from "fs/promises";
|
|
2383
|
+
import path13 from "path";
|
|
2384
|
+
function isContained(root, candidate) {
|
|
2385
|
+
return candidate === root || candidate.startsWith(`${root}${path13.sep}`);
|
|
2386
|
+
}
|
|
2387
|
+
var TechnicalPlanValidator = class {
|
|
2388
|
+
cwd;
|
|
2389
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
2390
|
+
this.cwd = path13.resolve(cwd);
|
|
2391
|
+
}
|
|
2392
|
+
async validateScopedPath(candidate, forbiddenPaths) {
|
|
2393
|
+
if (path13.isAbsolute(candidate)) return `Absolute path is not allowed: '${candidate}'.`;
|
|
2394
|
+
const normalized = candidate.replaceAll("\\", "/");
|
|
2395
|
+
if (!normalized || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
2396
|
+
return `Path must be normalized without empty, '.' or '..' segments: '${candidate}'.`;
|
|
2397
|
+
}
|
|
2398
|
+
if (normalized === ".git" || normalized.startsWith(".git/") || normalized === ".ai-workflow" || normalized.startsWith(".ai-workflow/")) {
|
|
2399
|
+
return `Workflow control path is out of implementation scope: '${candidate}'.`;
|
|
2400
|
+
}
|
|
2401
|
+
const conflictsWithProtectedPath = [...forbiddenPaths].some(
|
|
2402
|
+
(protectedPath) => normalized === protectedPath || normalized.startsWith(`${protectedPath}/`) || protectedPath.startsWith(`${normalized}/`)
|
|
2403
|
+
);
|
|
2404
|
+
if (normalized === "EVIDENCE.json" || conflictsWithProtectedPath) {
|
|
2405
|
+
return `Protected workflow artifact is out of implementation scope: '${candidate}'.`;
|
|
2406
|
+
}
|
|
2407
|
+
const absolute = path13.resolve(this.cwd, normalized);
|
|
2408
|
+
if (!isContained(this.cwd, absolute)) return `Path escapes repository: '${candidate}'.`;
|
|
2409
|
+
let cursor = this.cwd;
|
|
2410
|
+
for (const part of normalized.split("/")) {
|
|
2411
|
+
cursor = path13.join(cursor, part);
|
|
2412
|
+
try {
|
|
2413
|
+
const stat = await fs11.lstat(cursor);
|
|
2414
|
+
if (cursor === absolute && stat.isDirectory()) {
|
|
2415
|
+
return `filesInScope must name explicit files, not a directory: '${candidate}'.`;
|
|
2416
|
+
}
|
|
2417
|
+
if (stat.isSymbolicLink()) {
|
|
2418
|
+
const resolved = await fs11.realpath(cursor);
|
|
2419
|
+
if (!isContained(this.cwd, resolved)) return `Symlink escapes repository: '${candidate}'.`;
|
|
2420
|
+
cursor = resolved;
|
|
2421
|
+
}
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
if (error.code !== "ENOENT") return `Cannot inspect scoped path '${candidate}': ${error.message}`;
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
async validateOutput(output, protectedPaths = []) {
|
|
2430
|
+
let plan;
|
|
2431
|
+
try {
|
|
2432
|
+
plan = JSON.parse(stripMarkdownFence(output));
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
return { valid: false, reason: `Technical plan is not valid JSON: ${error.message}` };
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
const schema = JSON.parse(await fs11.readFile(path13.join(getPackageRoot(), "dist-assets/schemas/technical-plan.schema.json"), "utf8"));
|
|
2438
|
+
const validateSchema = new import_ajv2.default({ allErrors: true }).compile(schema);
|
|
2439
|
+
if (!validateSchema(plan)) {
|
|
2440
|
+
const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
|
|
2441
|
+
return { valid: false, reason: `Technical plan schema violation: ${errors}` };
|
|
2442
|
+
}
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
return { valid: false, reason: `Technical plan schema validation failed: ${error.message}` };
|
|
2445
|
+
}
|
|
2446
|
+
if (plan.filesInScope.length === 0) {
|
|
2447
|
+
return { valid: false, reason: "Technical plan filesInScope must not be empty." };
|
|
2448
|
+
}
|
|
2449
|
+
const forbidden = new Set(protectedPaths.map((entry) => path13.relative(this.cwd, path13.resolve(this.cwd, entry)).replaceAll("\\", "/")));
|
|
2450
|
+
for (const entry of plan.filesInScope) {
|
|
2451
|
+
const reason = await this.validateScopedPath(entry.path, forbidden);
|
|
2452
|
+
if (reason) return { valid: false, reason };
|
|
2453
|
+
}
|
|
2454
|
+
const content = `${JSON.stringify(plan, null, 2)}
|
|
2455
|
+
`;
|
|
2456
|
+
return { valid: true, plan, content };
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2459
|
+
|
|
2460
|
+
// src/cli/commands/run.ts
|
|
2461
|
+
import { execFileSync } from "child_process";
|
|
1953
2462
|
function normalizeTaskSlugFromSpec(specPath) {
|
|
1954
2463
|
const normalized = String(specPath).replaceAll("\\", "/");
|
|
1955
2464
|
const marker = "/docs/workflows/";
|
|
@@ -1958,7 +2467,7 @@ function normalizeTaskSlugFromSpec(specPath) {
|
|
|
1958
2467
|
const slug = normalized.slice(index).split("/")[0];
|
|
1959
2468
|
if (slug) return slug;
|
|
1960
2469
|
}
|
|
1961
|
-
return
|
|
2470
|
+
return path14.basename(specPath, path14.extname(specPath));
|
|
1962
2471
|
}
|
|
1963
2472
|
function combineValidation(evidence, quality) {
|
|
1964
2473
|
const statuses = [evidence.internalStatus, quality.overallStatus];
|
|
@@ -1970,27 +2479,197 @@ function combineValidation(evidence, quality) {
|
|
|
1970
2479
|
else if (statuses.includes("PASS_WITH_NOTES")) overallStatus = "PASS_WITH_NOTES";
|
|
1971
2480
|
return { overallStatus, evidence, quality };
|
|
1972
2481
|
}
|
|
1973
|
-
|
|
2482
|
+
function repositoryRelative(cwd, candidate) {
|
|
2483
|
+
return path14.relative(cwd, path14.resolve(cwd, candidate)).replaceAll("\\", "/");
|
|
2484
|
+
}
|
|
2485
|
+
async function findCompatibleLedger(cwd, specRelativePath) {
|
|
2486
|
+
const historyDir = path14.join(cwd, ".ai-workflow/history");
|
|
2487
|
+
const matches = [];
|
|
2488
|
+
for (const file of await fs12.readdir(historyDir).catch(() => [])) {
|
|
2489
|
+
if (!file.endsWith("-ledger.json")) continue;
|
|
2490
|
+
try {
|
|
2491
|
+
const events = JSON.parse(await fs12.readFile(path14.join(historyDir, file), "utf8"));
|
|
2492
|
+
if (events.some((event) => event.eventType === "specification_complete" && event.data?.artifactPath === specRelativePath)) {
|
|
2493
|
+
matches.push(path14.join(historyDir, file));
|
|
2494
|
+
}
|
|
2495
|
+
} catch {
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return matches;
|
|
2499
|
+
}
|
|
2500
|
+
async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) {
|
|
2501
|
+
if (!approvalPath) throw new Error("[SDD BLOCKED] DEEP resume requires --approval-path.");
|
|
2502
|
+
const approvalValidator = new ApprovalReceiptValidator({ cwd });
|
|
2503
|
+
const approval = await approvalValidator.validate(approvalPath, absoluteSpecPath);
|
|
2504
|
+
if (!approval.valid || !approval.receipt || !approval.approvedSpecHash) {
|
|
2505
|
+
throw new Error(`[SDD BLOCKED] ${approval.reason || "Approval receipt is invalid."}`);
|
|
2506
|
+
}
|
|
2507
|
+
const workflowId = approval.receipt.workflowId;
|
|
2508
|
+
const ledgerPath = `.ai-workflow/history/${workflowId}-ledger.json`;
|
|
2509
|
+
const absoluteLedgerPath = path14.join(cwd, ledgerPath);
|
|
2510
|
+
const relativeSpecPath = repositoryRelative(cwd, specPath);
|
|
2511
|
+
const compatibleLedgers = await findCompatibleLedger(cwd, relativeSpecPath);
|
|
2512
|
+
if (compatibleLedgers.length > 1) {
|
|
2513
|
+
throw new Error("[SDD BLOCKED] Multiple ledgers claim the same approved specification.");
|
|
2514
|
+
}
|
|
2515
|
+
if (compatibleLedgers.length === 1 && path14.resolve(compatibleLedgers[0]) !== path14.resolve(absoluteLedgerPath)) {
|
|
2516
|
+
throw new Error("[SDD BLOCKED] Approval workflowId conflicts with the ledger that created the specification.");
|
|
2517
|
+
}
|
|
2518
|
+
const ledger = new EvidenceLedger({ cwd, workflowId });
|
|
2519
|
+
const loaded = await ledger.load(ledgerPath);
|
|
2520
|
+
if (!loaded) {
|
|
2521
|
+
ledger.logEvent({
|
|
2522
|
+
actor: "Atlas",
|
|
2523
|
+
actorType: "control-plane",
|
|
2524
|
+
observed: true,
|
|
2525
|
+
eventType: "workflow_attached",
|
|
2526
|
+
provenance: "sdd-control-plane",
|
|
2527
|
+
data: { specSource: "external", specPath: relativeSpecPath, status: "COMPLETED" }
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
const events = ledger.getEvents();
|
|
2531
|
+
const approvalEvents = events.filter((event) => event.eventType === "spec_approval_verified");
|
|
2532
|
+
if (approvalEvents.length > 1) throw new Error("[SDD BLOCKED] Duplicate specification approval events in ledger.");
|
|
2533
|
+
if (approvalEvents.length === 1 && (approvalEvents[0].data?.approvedHash !== approval.approvedSpecHash || approvalEvents[0].data?.approvalPath !== repositoryRelative(cwd, approvalPath))) throw new Error("[SDD BLOCKED] Existing specification approval conflicts with the supplied receipt.");
|
|
2534
|
+
const nexusTerminals = events.filter((event) => event.eventType === "specification_complete");
|
|
2535
|
+
if (nexusTerminals.length > 1) throw new Error("[SDD BLOCKED] Duplicate Nexus terminal events in ledger.");
|
|
2536
|
+
if (nexusTerminals.length === 1 && nexusTerminals[0].data?.artifactPath !== relativeSpecPath) {
|
|
2537
|
+
throw new Error("[SDD BLOCKED] Ledger specification path does not match --spec-path.");
|
|
2538
|
+
}
|
|
2539
|
+
const draftHash = nexusTerminals[0]?.data?.artifactHash || null;
|
|
2540
|
+
const controller = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
|
|
2541
|
+
const absolutePlanPath = path14.join(cwd, "docs/workflows", workflowId, "technical-plan.json");
|
|
2542
|
+
const relativePlanPath = repositoryRelative(cwd, absolutePlanPath);
|
|
2543
|
+
if (await fs12.access(absolutePlanPath).then(() => true).catch(() => false)) {
|
|
2544
|
+
throw new Error(`[SDD BLOCKED] Refusing to overwrite existing technical plan: ${relativePlanPath}`);
|
|
2545
|
+
}
|
|
2546
|
+
const planValidator = new TechnicalPlanValidator({ cwd });
|
|
2547
|
+
const protectedPaths = [absoluteSpecPath, approvalPath, ledgerPath, absolutePlanPath, "EVIDENCE.json"];
|
|
2548
|
+
const planning = await controller.executePhase({
|
|
2549
|
+
phase: "planning",
|
|
2550
|
+
actor: "Orion",
|
|
2551
|
+
prompt: [
|
|
2552
|
+
"Return only a JSON technical plan conforming to dist-assets/schemas/technical-plan.schema.json.",
|
|
2553
|
+
"Use explicit repository-relative file paths in filesInScope. Do not edit files or implement.",
|
|
2554
|
+
"Approved specification:",
|
|
2555
|
+
specContent
|
|
2556
|
+
].join("\n\n"),
|
|
2557
|
+
readOnly: true,
|
|
2558
|
+
requireExplicitActor: true,
|
|
2559
|
+
onOutput: async (output) => {
|
|
2560
|
+
const validation = await planValidator.validateOutput(output, protectedPaths);
|
|
2561
|
+
if (!validation.valid || !validation.content) throw new Error(validation.reason || "Technical plan is invalid.");
|
|
2562
|
+
await writeFileAtomic(absolutePlanPath, validation.content);
|
|
2563
|
+
return { artifactPath: relativePlanPath, artifactHash: sha256(validation.content) };
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
await ledger.save(ledgerPath);
|
|
2567
|
+
if (!planning.success || !planning.artifact?.artifactHash) {
|
|
2568
|
+
throw new Error(`[WORKFLOW BLOCKED] Orion planning phase failed: ${planning.error || "unknown failure"}`);
|
|
2569
|
+
}
|
|
2570
|
+
if (approvalEvents.length === 0) {
|
|
2571
|
+
ledger.logEvent({
|
|
2572
|
+
actor: "Atlas",
|
|
2573
|
+
actorType: "control-plane",
|
|
2574
|
+
observed: true,
|
|
2575
|
+
eventType: "spec_approval_verified",
|
|
2576
|
+
provenance: "approval-receipt",
|
|
2577
|
+
data: {
|
|
2578
|
+
approvalPath: repositoryRelative(cwd, approvalPath),
|
|
2579
|
+
approvedHash: approval.approvedSpecHash,
|
|
2580
|
+
draftHash,
|
|
2581
|
+
authorizationSource: approval.receipt.authorizationSource,
|
|
2582
|
+
status: "COMPLETED"
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2585
|
+
await ledger.save(ledgerPath);
|
|
2586
|
+
}
|
|
2587
|
+
const planContent = await fs12.readFile(absolutePlanPath, "utf8");
|
|
2588
|
+
const parsedPlan = JSON.parse(planContent);
|
|
2589
|
+
const approvedHash = approval.approvedSpecHash;
|
|
2590
|
+
const planHash = String(planning.artifact.artifactHash);
|
|
2591
|
+
const baseSha = (() => {
|
|
2592
|
+
try {
|
|
2593
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", stdio: "pipe" }).trim();
|
|
2594
|
+
} catch {
|
|
2595
|
+
return "unknown";
|
|
2596
|
+
}
|
|
2597
|
+
})();
|
|
2598
|
+
const finalizer = new Finalizer({ cwd, fastTrack: false });
|
|
2599
|
+
const beforeImplementation = await finalizer.snapshotManager.capture();
|
|
2600
|
+
const allowedFiles = new Set(parsedPlan.filesInScope.map((entry) => String(entry.path).replaceAll("\\", "/")));
|
|
2601
|
+
const implementation = await controller.executePhase({
|
|
2602
|
+
phase: "implementation",
|
|
2603
|
+
actor: "Astra",
|
|
2604
|
+
prompt: [
|
|
2605
|
+
"Implement the approved specification using the validated technical plan.",
|
|
2606
|
+
`You may modify only these files: ${[...allowedFiles].join(", ")}`,
|
|
2607
|
+
"Do not modify the specification, approval receipt, technical plan, ledger, or evidence artifacts.",
|
|
2608
|
+
"Approved specification:",
|
|
2609
|
+
specContent,
|
|
2610
|
+
"Validated technical plan:",
|
|
2611
|
+
planContent
|
|
2612
|
+
].join("\n\n"),
|
|
2613
|
+
readOnly: false,
|
|
2614
|
+
requireExplicitActor: true,
|
|
2615
|
+
onOutput: async () => {
|
|
2616
|
+
if (await hashFile(absoluteSpecPath) !== approvedHash) throw new Error("Approved specification changed during Astra implementation.");
|
|
2617
|
+
if (await hashFile(absolutePlanPath) !== planHash) throw new Error("Technical plan changed during Astra implementation.");
|
|
2618
|
+
const integrity = await finalizer.verifyIntegrity(beforeImplementation);
|
|
2619
|
+
const workspaceStateMarkers = /* @__PURE__ */ new Set(["stagedDiff", "unstagedDiff", "untrackedFiles"]);
|
|
2620
|
+
const changed = [...integrity.changes.added, ...integrity.changes.modified, ...integrity.changes.deleted].filter((entry) => !workspaceStateMarkers.has(entry) && !/^(branch|head|indexTree):/.test(entry)).map((entry) => String(entry).replaceAll("\\", "/"));
|
|
2621
|
+
const outsideScope = changed.filter((entry) => !allowedFiles.has(entry));
|
|
2622
|
+
if (outsideScope.length > 0) throw new Error(`Astra modified files outside the technical plan: ${outsideScope.join(", ")}`);
|
|
2623
|
+
return { artifactPath: relativePlanPath, artifactHash: planHash };
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
await ledger.save(ledgerPath);
|
|
2627
|
+
if (!implementation.success) {
|
|
2628
|
+
throw new Error(`[WORKFLOW BLOCKED] Astra implementation phase failed: ${implementation.error || "unknown failure"}`);
|
|
2629
|
+
}
|
|
2630
|
+
const phaseEvents = ledger.getEvents().filter(
|
|
2631
|
+
(event) => ["specification_complete", "planning_complete", "implementation_complete"].includes(event.eventType) && event.data?.success !== false
|
|
2632
|
+
);
|
|
2633
|
+
return {
|
|
2634
|
+
workflowId,
|
|
2635
|
+
baseSha,
|
|
2636
|
+
artifactHashes: { draft: draftHash, approved: approvedHash, technicalPlan: planHash },
|
|
2637
|
+
phaseOrder: phaseEvents.map((event) => event.data?.phase || event.eventType.replace("_complete", "")),
|
|
2638
|
+
confirmedActors: phaseEvents.filter((event) => event.data?.runtimeAgentApplied).map((event) => ({
|
|
2639
|
+
phase: event.data.phase || event.eventType.replace("_complete", ""),
|
|
2640
|
+
requested: event.data.runtimeAgentRequested,
|
|
2641
|
+
applied: event.data.runtimeAgentApplied
|
|
2642
|
+
})),
|
|
2643
|
+
ledger,
|
|
2644
|
+
ledgerPath,
|
|
2645
|
+
controller
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null }) {
|
|
2649
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
2650
|
+
throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
|
|
2651
|
+
}
|
|
1974
2652
|
console.log("\n[AI WORKFLOW] Starting evidence-backed orchestration...\n");
|
|
1975
|
-
const branchGate = new BranchGate({ memoryDir:
|
|
2653
|
+
const branchGate = new BranchGate({ memoryDir: path14.join(cwd, ".ai-workflow"), cwd });
|
|
1976
2654
|
const taskSlug = specPath ? normalizeTaskSlugFromSpec(specPath) : "implementation";
|
|
1977
2655
|
const gateResult = branchGate.check(override, { taskSlug });
|
|
1978
2656
|
if (gateResult.blocked) throw new Error(`[GATE BLOCKED] ${gateResult.reason}`);
|
|
1979
2657
|
console.log(`[PASS] Branch Gate: ${gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : `${gateResult.branch} is authorized`}.`);
|
|
1980
2658
|
if (!specPath) throw new Error("[SDD BLOCKED] Missing --spec-path. An approved specification is required.");
|
|
1981
2659
|
const validator = new SpecValidator();
|
|
1982
|
-
const absoluteSpecPath =
|
|
2660
|
+
const absoluteSpecPath = path14.isAbsolute(specPath) ? specPath : path14.join(cwd, specPath);
|
|
1983
2661
|
const validation = await validator.validate(absoluteSpecPath);
|
|
1984
2662
|
if (!validation.valid) throw new Error(`[SDD BLOCKED] Specification is not ready: ${validation.reason}`);
|
|
1985
2663
|
const specTier = validation.tier || "standard";
|
|
1986
2664
|
const riskLevel = specTier === "deep" ? "high" : specTier === "tiny" ? "low" : "medium";
|
|
1987
2665
|
const evidencePolicy = specTier === "deep" ? "required" : "optional";
|
|
1988
2666
|
const remediationLimit = specTier === "deep" ? 3 : specTier === "tiny" ? 1 : 2;
|
|
1989
|
-
const specContent = await
|
|
2667
|
+
const specContent = await fs12.readFile(absoluteSpecPath, "utf8");
|
|
1990
2668
|
const workflowProfile = resolveWorkflowProfile({ request: specContent });
|
|
1991
2669
|
const profileDefinition = getWorkflowProfile(workflowProfile);
|
|
1992
2670
|
console.log(`[PASS] Specification: ${specTier.toUpperCase()} spec is APPROVED.`);
|
|
1993
2671
|
console.log(`[PROFILE] ${workflowProfile} -> ${profileDefinition.owner}; skills: ${profileDefinition.skills.join(", ") || "none"}.`);
|
|
2672
|
+
const deepContext = specTier === "deep" ? await runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) : null;
|
|
1994
2673
|
const validateWorkflow = async () => {
|
|
1995
2674
|
const evidence = await runCollectEvidence({
|
|
1996
2675
|
cwd,
|
|
@@ -1998,18 +2677,36 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
1998
2677
|
taskSlug,
|
|
1999
2678
|
evidencePolicy,
|
|
2000
2679
|
profile: workflowProfile,
|
|
2001
|
-
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED"
|
|
2680
|
+
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
|
|
2681
|
+
workflowEvidence: deepContext ? {
|
|
2682
|
+
workflowId: deepContext.workflowId,
|
|
2683
|
+
baseSha: deepContext.baseSha,
|
|
2684
|
+
artifactHashes: deepContext.artifactHashes,
|
|
2685
|
+
phaseOrder: deepContext.phaseOrder,
|
|
2686
|
+
confirmedActors: deepContext.confirmedActors
|
|
2687
|
+
} : null
|
|
2002
2688
|
});
|
|
2003
2689
|
const quality = await new QualityGuard({ cwd, taskSlug, evidencePolicy, riskLevel }).verify();
|
|
2004
|
-
|
|
2690
|
+
const combined = combineValidation(evidence, quality);
|
|
2691
|
+
if (deepContext) {
|
|
2692
|
+
const workflowEvidence = await new EvidenceValidator({ cwd, requireWorkflowEvidence: true }).validate();
|
|
2693
|
+
if (!workflowEvidence.valid) {
|
|
2694
|
+
combined.overallStatus = "FAIL_QUALITY_GATE";
|
|
2695
|
+
combined.reason = workflowEvidence.reason;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
return combined;
|
|
2005
2699
|
};
|
|
2006
|
-
|
|
2700
|
+
const observedValidate = async () => deepContext ? await deepContext.controller.validate(taskSlug, workflowProfile, validateWorkflow, evidencePolicy) : await validateWorkflow();
|
|
2701
|
+
let result = await observedValidate();
|
|
2702
|
+
if (deepContext) await deepContext.ledger.save(deepContext.ledgerPath);
|
|
2007
2703
|
if (isRecoverableGateFailure(result.overallStatus)) {
|
|
2008
2704
|
console.log(`
|
|
2009
2705
|
[REMEDIATION REQUIRED] ${result.overallStatus}. Starting bounded remediation (limit: ${remediationLimit}).`);
|
|
2010
2706
|
const executor = typeof remediationExecutor === "function" ? remediationExecutor : createCliRemediationExecutor(cwd);
|
|
2011
2707
|
const healer = new HealerEngine({ cwd, remediationLimit, taskSlug });
|
|
2012
|
-
result = await healer.run({ initialResult: result, validate:
|
|
2708
|
+
result = await healer.run({ initialResult: result, validate: observedValidate, remediate: executor });
|
|
2709
|
+
if (deepContext) await deepContext.ledger.save(deepContext.ledgerPath);
|
|
2013
2710
|
}
|
|
2014
2711
|
if (isTerminalFailure(result.overallStatus)) {
|
|
2015
2712
|
throw new Error(`[WORKFLOW BLOCKED] ${result.overallStatus}: ${result.reason || "Validation could not be resolved safely."}`);
|
|
@@ -2020,7 +2717,7 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
2020
2717
|
console.log("\n--- Final Handoff ---");
|
|
2021
2718
|
const handoffEngine = new HandoffEngine({ cwd });
|
|
2022
2719
|
const handoffPath = await handoffEngine.generate({
|
|
2023
|
-
taskId:
|
|
2720
|
+
taskId: path14.basename(specPath, ".md"),
|
|
2024
2721
|
status: result.overallStatus,
|
|
2025
2722
|
specPaths: [specPath],
|
|
2026
2723
|
evidence: result.evidence,
|
|
@@ -2029,14 +2726,14 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
2029
2726
|
console.log(`
|
|
2030
2727
|
[AI WORKFLOW COMPLETE] ${result.overallStatus}`);
|
|
2031
2728
|
if (result.remediation?.attempts) console.log(`Remediation attempts: ${result.remediation.attempts}`);
|
|
2032
|
-
console.log(`Handoff Packet: ${
|
|
2729
|
+
console.log(`Handoff Packet: ${path14.relative(cwd, handoffPath)}
|
|
2033
2730
|
`);
|
|
2034
|
-
return { ...result, workflowProfile };
|
|
2731
|
+
return { ...result, workflowProfile, workflowId: deepContext?.workflowId || null };
|
|
2035
2732
|
}
|
|
2036
2733
|
|
|
2037
2734
|
// src/core/healing/runtime-remediation-executor.ts
|
|
2038
|
-
import
|
|
2039
|
-
import
|
|
2735
|
+
import fs13 from "fs/promises";
|
|
2736
|
+
import path15 from "path";
|
|
2040
2737
|
import { execSync as execSync3 } from "child_process";
|
|
2041
2738
|
function createRuntimeRemediationExecutor(cwd, ledger = null) {
|
|
2042
2739
|
const adapter = new OpenCodeAdapter({ cwd });
|
|
@@ -2076,14 +2773,14 @@ function createRuntimeRemediationExecutor(cwd, ledger = null) {
|
|
|
2076
2773
|
}
|
|
2077
2774
|
let requestContent = {};
|
|
2078
2775
|
try {
|
|
2079
|
-
requestContent = JSON.parse(await
|
|
2776
|
+
requestContent = JSON.parse(await fs13.readFile(requestPath, "utf8"));
|
|
2080
2777
|
} catch {
|
|
2081
2778
|
}
|
|
2082
2779
|
const filesBefore = getChangedFiles2();
|
|
2083
2780
|
const fileContentsBefore = {};
|
|
2084
2781
|
for (const file of filesBefore) {
|
|
2085
2782
|
try {
|
|
2086
|
-
fileContentsBefore[file] = await
|
|
2783
|
+
fileContentsBefore[file] = await fs13.readFile(path15.join(cwd, file), "utf8");
|
|
2087
2784
|
} catch {
|
|
2088
2785
|
fileContentsBefore[file] = null;
|
|
2089
2786
|
}
|
|
@@ -2121,7 +2818,7 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
|
|
|
2121
2818
|
changedFiles.push(file);
|
|
2122
2819
|
} else {
|
|
2123
2820
|
try {
|
|
2124
|
-
const contentAfter = await
|
|
2821
|
+
const contentAfter = await fs13.readFile(path15.join(cwd, file), "utf8");
|
|
2125
2822
|
if (contentAfter !== fileContentsBefore[file]) {
|
|
2126
2823
|
changedFiles.push(file);
|
|
2127
2824
|
}
|
|
@@ -2158,9 +2855,22 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
|
|
|
2158
2855
|
}
|
|
2159
2856
|
|
|
2160
2857
|
// src/cli/commands/execute.ts
|
|
2161
|
-
import
|
|
2162
|
-
import
|
|
2858
|
+
import fs14 from "fs/promises";
|
|
2859
|
+
import path16 from "path";
|
|
2163
2860
|
import { execSync as execSync4 } from "child_process";
|
|
2861
|
+
var INTEGRITY_METADATA_CHANGES = /* @__PURE__ */ new Set(["stagedDiff", "unstagedDiff", "untrackedFiles"]);
|
|
2862
|
+
function extractDeliveryChangedFiles(integrity) {
|
|
2863
|
+
const files = /* @__PURE__ */ new Set();
|
|
2864
|
+
for (const file of integrity.changes.added) files.add(file);
|
|
2865
|
+
for (const file of integrity.changes.deleted) files.add(file);
|
|
2866
|
+
for (const change of integrity.changes.modified) {
|
|
2867
|
+
if (change.startsWith("branch:") || change.startsWith("head:")) continue;
|
|
2868
|
+
if (INTEGRITY_METADATA_CHANGES.has(change)) continue;
|
|
2869
|
+
if (change.startsWith("indexTree:")) continue;
|
|
2870
|
+
files.add(change);
|
|
2871
|
+
}
|
|
2872
|
+
return [...files].sort();
|
|
2873
|
+
}
|
|
2164
2874
|
async function captureReadOnlyState(cwd, fastTrack = false) {
|
|
2165
2875
|
const finalizer = new Finalizer({ cwd, fastTrack });
|
|
2166
2876
|
const snapshot = await finalizer.snapshotManager.capture();
|
|
@@ -2248,6 +2958,13 @@ async function classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegatio
|
|
|
2248
2958
|
const plan = planner.plan(classification, taskSlug);
|
|
2249
2959
|
console.log(`[PLANNED] Owner: ${plan.owner}, Remediation limit: ${plan.remediationLimit}`);
|
|
2250
2960
|
stateMachine.transitionTo("PLANNED");
|
|
2961
|
+
if (classification.requestUnderstanding.deliveryMode === "workspace") {
|
|
2962
|
+
const deliveryDecision = await new DeliveryDecisionEngine({ cwd }).determineDecision({ userRequest: naturalRequest });
|
|
2963
|
+
if (deliveryDecision.decision.startsWith("BLOCK_")) {
|
|
2964
|
+
stateMachine.transitionTo("BLOCKED");
|
|
2965
|
+
throw new Error(`[CONTEXT BLOCKED] NEEDS_CLARIFICATION: ${deliveryDecision.reason || deliveryDecision.decision}`);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2251
2968
|
return { plan, classification };
|
|
2252
2969
|
}
|
|
2253
2970
|
function runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate) {
|
|
@@ -2265,12 +2982,12 @@ function runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate) {
|
|
|
2265
2982
|
}
|
|
2266
2983
|
async function createSpecIfRequired(plan, classification, taskSlug, cwd) {
|
|
2267
2984
|
if (plan.specPath) {
|
|
2268
|
-
const fullSpecPath =
|
|
2269
|
-
const specExists = await
|
|
2985
|
+
const fullSpecPath = path16.join(cwd, plan.specPath);
|
|
2986
|
+
const specExists = await fs14.access(fullSpecPath).then(() => true).catch(() => false);
|
|
2270
2987
|
if (!specExists) {
|
|
2271
|
-
await
|
|
2272
|
-
const specTemplatePath =
|
|
2273
|
-
const templateContent = await
|
|
2988
|
+
await fs14.mkdir(path16.dirname(fullSpecPath), { recursive: true });
|
|
2989
|
+
const specTemplatePath = path16.join(cwd, ".ai-workflow/templates/specs/standard.md");
|
|
2990
|
+
const templateContent = await fs14.readFile(specTemplatePath, "utf8").catch(() => {
|
|
2274
2991
|
return `# [STANDARD] Specification: ${classification.request}
|
|
2275
2992
|
|
|
2276
2993
|
## Metadata
|
|
@@ -2298,11 +3015,87 @@ async function createSpecIfRequired(plan, classification, taskSlug, cwd) {
|
|
|
2298
3015
|
|
|
2299
3016
|
- [ ] Behavior tests pass`;
|
|
2300
3017
|
});
|
|
2301
|
-
await
|
|
3018
|
+
await fs14.writeFile(fullSpecPath, templateContent);
|
|
2302
3019
|
console.log(`[EXECUTE] Created DRAFT specification template at: ${plan.specPath}`);
|
|
2303
3020
|
}
|
|
2304
3021
|
}
|
|
2305
3022
|
}
|
|
3023
|
+
async function runDeepSpecificationCheckpoint(plan, classification, taskSlug, cwd, delegationController, stateMachine) {
|
|
3024
|
+
if (!plan.specPath) throw new Error("[SDD BLOCKED] Required specification path was not planned.");
|
|
3025
|
+
const absoluteSpecPath = path16.resolve(cwd, plan.specPath);
|
|
3026
|
+
const relativeSpecPath = path16.relative(cwd, absoluteSpecPath).replaceAll("\\", "/");
|
|
3027
|
+
if (relativeSpecPath.startsWith("../") || path16.isAbsolute(relativeSpecPath)) {
|
|
3028
|
+
throw new Error("[SDD BLOCKED] Planned specification path escapes the repository.");
|
|
3029
|
+
}
|
|
3030
|
+
if (await fs14.access(absoluteSpecPath).then(() => true).catch(() => false)) {
|
|
3031
|
+
throw new Error(`[SDD BLOCKED] Refusing to overwrite existing specification: ${relativeSpecPath}`);
|
|
3032
|
+
}
|
|
3033
|
+
const approvalPath = `.ai-workflow/approvals/${taskSlug}.json`;
|
|
3034
|
+
const validator = new SpecValidator();
|
|
3035
|
+
stateMachine.transitionTo("DELEGATED");
|
|
3036
|
+
const prompt = [
|
|
3037
|
+
"Produce only the complete Markdown for a [DEEP] specification.",
|
|
3038
|
+
"The Metadata Status must be exactly DRAFT. Include concrete acceptance criteria as unchecked checkboxes.",
|
|
3039
|
+
"Do not approve the specification, create a receipt, edit files, or implement code.",
|
|
3040
|
+
`Workflow ID: ${taskSlug}`,
|
|
3041
|
+
`Request: ${classification.request}`
|
|
3042
|
+
].join("\n");
|
|
3043
|
+
const phaseResult = await delegationController.executePhase({
|
|
3044
|
+
phase: "specification",
|
|
3045
|
+
actor: "Nexus",
|
|
3046
|
+
prompt,
|
|
3047
|
+
readOnly: true,
|
|
3048
|
+
requireExplicitActor: true,
|
|
3049
|
+
onOutput: async (output) => {
|
|
3050
|
+
const content = stripMarkdownFence(output);
|
|
3051
|
+
const validation = validator.validateDraftContent(content);
|
|
3052
|
+
if (!validation.valid) throw new Error(`Nexus DRAFT rejected: ${validation.reason}`);
|
|
3053
|
+
await writeFileAtomic(absoluteSpecPath, content);
|
|
3054
|
+
return { artifactPath: relativeSpecPath, artifactHash: sha256(content), draftHash: sha256(content) };
|
|
3055
|
+
}
|
|
3056
|
+
});
|
|
3057
|
+
if (!phaseResult.success) {
|
|
3058
|
+
stateMachine.transitionTo("BLOCKED");
|
|
3059
|
+
throw new Error(`[SDD BLOCKED] Nexus specification phase failed: ${phaseResult.error || "unknown failure"}`);
|
|
3060
|
+
}
|
|
3061
|
+
const draftHash = String(phaseResult.artifact?.artifactHash || "");
|
|
3062
|
+
delegationController.ledger?.logEvent({
|
|
3063
|
+
actor: "Atlas",
|
|
3064
|
+
actorType: "control-plane",
|
|
3065
|
+
observed: true,
|
|
3066
|
+
eventType: "approval_checkpoint",
|
|
3067
|
+
provenance: "sdd-control-plane",
|
|
3068
|
+
data: {
|
|
3069
|
+
specSource: "nexus",
|
|
3070
|
+
specPath: relativeSpecPath,
|
|
3071
|
+
approvalPath,
|
|
3072
|
+
draftHash,
|
|
3073
|
+
approvedHash: null,
|
|
3074
|
+
planHash: null,
|
|
3075
|
+
status: "BLOCKED",
|
|
3076
|
+
reason: "SDD APPROVAL REQUIRED"
|
|
3077
|
+
}
|
|
3078
|
+
});
|
|
3079
|
+
stateMachine.transitionTo("BLOCKED");
|
|
3080
|
+
const resumeCommand = `aw run --spec-path=${relativeSpecPath} --approval-path=${approvalPath}`;
|
|
3081
|
+
console.log(`
|
|
3082
|
+
[BLOCKED] SDD APPROVAL REQUIRED`);
|
|
3083
|
+
console.log(`Specification: ${relativeSpecPath}`);
|
|
3084
|
+
console.log(`Resume: ${resumeCommand}
|
|
3085
|
+
`);
|
|
3086
|
+
return {
|
|
3087
|
+
overallStatus: "BLOCKED",
|
|
3088
|
+
reason: "SDD APPROVAL REQUIRED",
|
|
3089
|
+
checkpoint: "approval",
|
|
3090
|
+
exitCode: 2,
|
|
3091
|
+
workflowId: taskSlug,
|
|
3092
|
+
specPath: relativeSpecPath,
|
|
3093
|
+
approvalPath,
|
|
3094
|
+
draftHash,
|
|
3095
|
+
resumeCommand,
|
|
3096
|
+
stateHistory: stateMachine.getHistory()
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
2306
3099
|
async function runImplementation(plan, classification, delegationController, stateMachine, fastTrack = false) {
|
|
2307
3100
|
stateMachine.transitionTo("DELEGATED");
|
|
2308
3101
|
stateMachine.transitionTo("IMPLEMENTING");
|
|
@@ -2310,7 +3103,21 @@ async function runImplementation(plan, classification, delegationController, sta
|
|
|
2310
3103
|
if (plan.specPath) {
|
|
2311
3104
|
promptMsg = `Please review and fill in the specification file at: ${plan.specPath}. Make sure to change the Status field to 'APPROVED' in the Metadata section, and then implement the behavior described.`;
|
|
2312
3105
|
}
|
|
2313
|
-
|
|
3106
|
+
if (plan.branchNeeded) {
|
|
3107
|
+
promptMsg = [
|
|
3108
|
+
promptMsg,
|
|
3109
|
+
"",
|
|
3110
|
+
"AIWK delivery contract:",
|
|
3111
|
+
"- Work in the active repository; printed code is not a substitute for workspace delivery.",
|
|
3112
|
+
"- Keep the diff scoped and run proportional validation.",
|
|
3113
|
+
"- The parent already satisfied the branch gate. Do not repeat branch/status checks unless a new safety concern makes them necessary.",
|
|
3114
|
+
"- Documentation, screenshots, Sage review, Phoenix remediation, and persisted evidence are conditional; do not create them unless the request, risk policy, or an observed finding requires them.",
|
|
3115
|
+
"- Do not run collect-evidence, create a handoff, or finalize this workflow; the parent execute process is the sole finalization owner.",
|
|
3116
|
+
"- End with exactly one machine-readable line: AIWK_DELIVERY_OUTCOME: CHANGED or AIWK_DELIVERY_OUTCOME: ALREADY_SATISFIED.",
|
|
3117
|
+
"- Use ALREADY_SATISFIED only when inspection and relevant validation prove that no repository change is needed."
|
|
3118
|
+
].join("\n");
|
|
3119
|
+
}
|
|
3120
|
+
const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded, fastTrack, orchestratedChild: true });
|
|
2314
3121
|
if (!runResult.success) {
|
|
2315
3122
|
console.error(`[EXECUTE] OpenCode adapter execution reported failure: ${runResult.error || "Unknown error"}`);
|
|
2316
3123
|
stateMachine.transitionTo("BLOCKED");
|
|
@@ -2331,9 +3138,13 @@ async function runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachin
|
|
|
2331
3138
|
${violations.join("\n")}`);
|
|
2332
3139
|
}
|
|
2333
3140
|
}
|
|
2334
|
-
async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, delegationController, stateMachine) {
|
|
3141
|
+
async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, claimedOutcome, deliveryBaseline, deliveryFinalizer, delegationController, stateMachine) {
|
|
2335
3142
|
stateMachine.transitionTo("VALIDATING");
|
|
2336
3143
|
const validateWorkflow = async () => {
|
|
3144
|
+
const deliveryIntegrity = await deliveryFinalizer.verifyIntegrity(deliveryBaseline);
|
|
3145
|
+
const deliveryChangedFiles = extractDeliveryChangedFiles(deliveryIntegrity);
|
|
3146
|
+
const deliveryAddedFiles = deliveryIntegrity.changes.added.filter((file) => deliveryChangedFiles.includes(file));
|
|
3147
|
+
const deliveryDeletedFiles = deliveryIntegrity.changes.deleted.filter((file) => deliveryChangedFiles.includes(file));
|
|
2337
3148
|
const evidence = await runCollectEvidence({
|
|
2338
3149
|
cwd,
|
|
2339
3150
|
exitOnError: false,
|
|
@@ -2342,7 +3153,11 @@ async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, de
|
|
|
2342
3153
|
profile: plan.profile,
|
|
2343
3154
|
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
|
|
2344
3155
|
userRequest: naturalRequest,
|
|
2345
|
-
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
|
|
3156
|
+
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : [],
|
|
3157
|
+
claimedOutcome,
|
|
3158
|
+
deliveryChangedFiles,
|
|
3159
|
+
deliveryAddedFiles,
|
|
3160
|
+
deliveryDeletedFiles
|
|
2346
3161
|
});
|
|
2347
3162
|
const quality = await new QualityGuard({
|
|
2348
3163
|
cwd,
|
|
@@ -2415,7 +3230,7 @@ async function generateFinalHandoff(plan, taskSlug, cwd, finalState, evidence) {
|
|
|
2415
3230
|
});
|
|
2416
3231
|
console.log(`
|
|
2417
3232
|
[AI WORKFLOW COMPLETE] ${finalState}`);
|
|
2418
|
-
console.log(`Handoff Packet: ${
|
|
3233
|
+
console.log(`Handoff Packet: ${path16.relative(cwd, handoffPath)}
|
|
2419
3234
|
`);
|
|
2420
3235
|
return handoffPath;
|
|
2421
3236
|
}
|
|
@@ -2425,12 +3240,12 @@ async function runFastTrackValidation(cwd, result, checkBranchSafety) {
|
|
|
2425
3240
|
checkBranchSafety();
|
|
2426
3241
|
const changedFiles = result.evidence?.changedFiles || [];
|
|
2427
3242
|
console.log(`[FAST-TRACK] Changed files detected:`, changedFiles);
|
|
2428
|
-
const evidencePath =
|
|
2429
|
-
const hasEvidence = await
|
|
3243
|
+
const evidencePath = path16.join(cwd, "EVIDENCE.json");
|
|
3244
|
+
const hasEvidence = await fs14.access(evidencePath).then(() => true).catch(() => false);
|
|
2430
3245
|
if (hasEvidence) {
|
|
2431
|
-
const { EvidenceValidator } = await import("../evidence-validator-
|
|
3246
|
+
const { EvidenceValidator: EvidenceValidator2 } = await import("../evidence-validator-HS3NTWAB.js");
|
|
2432
3247
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
2433
|
-
const evidenceValidator = new
|
|
3248
|
+
const evidenceValidator = new EvidenceValidator2({ cwd, skipFileCheck: isTest, skipGitCheck: isTest });
|
|
2434
3249
|
const valResult = await evidenceValidator.validate();
|
|
2435
3250
|
if (!valResult.valid) {
|
|
2436
3251
|
console.error(`
|
|
@@ -2445,7 +3260,7 @@ async function runFidelityValidation(naturalRequest, result, finalizer) {
|
|
|
2445
3260
|
userRequest: naturalRequest,
|
|
2446
3261
|
projectContext: result.evidence?.deliveryDecision?.projectContext || null,
|
|
2447
3262
|
deliveryDecision: result.evidence?.deliveryDecision || null,
|
|
2448
|
-
changedFiles: result.evidence?.changedFiles || [],
|
|
3263
|
+
changedFiles: result.evidence?.deliveryChangedFiles || result.evidence?.changedFiles || [],
|
|
2449
3264
|
evidence: result.evidence || null,
|
|
2450
3265
|
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
|
|
2451
3266
|
});
|
|
@@ -2462,7 +3277,7 @@ function computeFinalState(finalStatus) {
|
|
|
2462
3277
|
if (finalStatus === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
|
|
2463
3278
|
return "BLOCKED";
|
|
2464
3279
|
}
|
|
2465
|
-
async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack) {
|
|
3280
|
+
async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, deliveryOutcome) {
|
|
2466
3281
|
await runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachine, fastTrack);
|
|
2467
3282
|
stateMachine.transitionTo("IMPLEMENTED");
|
|
2468
3283
|
stateMachine.transitionTo("VALIDATING");
|
|
@@ -2476,7 +3291,11 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
|
|
|
2476
3291
|
overallStatus: "PASS",
|
|
2477
3292
|
evidence: {
|
|
2478
3293
|
internalStatus: "PASS",
|
|
2479
|
-
commandsRun: []
|
|
3294
|
+
commandsRun: [],
|
|
3295
|
+
deliveryMode: "answer-only",
|
|
3296
|
+
deliveryOutcome,
|
|
3297
|
+
testAction: "none",
|
|
3298
|
+
testReason: "Read-only work does not change executable behavior."
|
|
2480
3299
|
},
|
|
2481
3300
|
quality: {
|
|
2482
3301
|
overallStatus: "PASS"
|
|
@@ -2485,6 +3304,9 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
|
|
|
2485
3304
|
};
|
|
2486
3305
|
}
|
|
2487
3306
|
async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
3307
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
3308
|
+
throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
|
|
3309
|
+
}
|
|
2488
3310
|
if (!naturalRequest || !naturalRequest.trim()) {
|
|
2489
3311
|
throw new Error("Missing request. Please provide a natural request string via positional arguments or --request flag.");
|
|
2490
3312
|
}
|
|
@@ -2495,13 +3317,15 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
2495
3317
|
const ledger = new EvidenceLedger({ cwd, workflowId: taskSlug });
|
|
2496
3318
|
const delegationController = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
|
|
2497
3319
|
let plan = null;
|
|
3320
|
+
let branchAuthorized = false;
|
|
2498
3321
|
try {
|
|
2499
3322
|
const stateMachine = new WorkflowStateMachine();
|
|
2500
3323
|
const { plan: planObj, classification } = await classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegationController, stateMachine);
|
|
2501
3324
|
plan = planObj;
|
|
2502
3325
|
const fastTrack = plan.riskLevel === "low" && (!plan.branchNeeded || plan.evidencePolicy === "optional");
|
|
2503
|
-
const branchGate = new BranchGate({ memoryDir:
|
|
3326
|
+
const branchGate = new BranchGate({ memoryDir: path16.join(cwd, ".ai-workflow"), cwd });
|
|
2504
3327
|
const gateResult = runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate);
|
|
3328
|
+
branchAuthorized = true;
|
|
2505
3329
|
const checkBranchSafety = () => {
|
|
2506
3330
|
if (!planObj.branchNeeded) return;
|
|
2507
3331
|
const currentBranch = branchGate.getCurrentBranch();
|
|
@@ -2510,20 +3334,43 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
2510
3334
|
throw new Error(`[WORKFLOW BLOCKED] Security violation: current branch is protected '${currentBranch}'!`);
|
|
2511
3335
|
}
|
|
2512
3336
|
};
|
|
3337
|
+
if (classification.specPolicy === "required") {
|
|
3338
|
+
return await runDeepSpecificationCheckpoint(plan, classification, taskSlug, cwd, delegationController, stateMachine);
|
|
3339
|
+
}
|
|
2513
3340
|
await createSpecIfRequired(plan, classification, taskSlug, cwd);
|
|
2514
3341
|
let readOnlyStateBefore = null;
|
|
3342
|
+
let deliveryBaseline = null;
|
|
3343
|
+
const deliveryFinalizer = new Finalizer({ cwd, fastTrack: false });
|
|
2515
3344
|
if (!plan.branchNeeded) {
|
|
2516
3345
|
readOnlyStateBefore = await captureReadOnlyState(cwd, fastTrack);
|
|
3346
|
+
} else {
|
|
3347
|
+
deliveryBaseline = await deliveryFinalizer.snapshotManager.capture();
|
|
2517
3348
|
}
|
|
2518
|
-
await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
|
|
3349
|
+
const implementationResult = await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
|
|
3350
|
+
const claimedOutcome = parseDeliveryOutcomeClaim(implementationResult.output || "");
|
|
2519
3351
|
if (!plan.branchNeeded && readOnlyStateBefore) {
|
|
2520
|
-
|
|
3352
|
+
const readOnlyOutcome = classification.requestUnderstanding.mutationIntent === "readonly" ? "READ_ONLY_RESULT" : "PREVIEW_ONLY";
|
|
3353
|
+
return await executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, readOnlyOutcome);
|
|
3354
|
+
}
|
|
3355
|
+
if (!deliveryBaseline) {
|
|
3356
|
+
throw new Error("[WORKFLOW BLOCKED] Missing delivery baseline for a write workflow.");
|
|
2521
3357
|
}
|
|
2522
3358
|
stateMachine.transitionTo("IMPLEMENTED");
|
|
2523
3359
|
checkBranchSafety();
|
|
2524
3360
|
const finalizer = new Finalizer({ cwd, fastTrack });
|
|
2525
3361
|
const snapshotRef = { current: await finalizer.snapshotManager.capture() };
|
|
2526
|
-
const { result: valResult, validateWorkflow } = await runValidation(
|
|
3362
|
+
const { result: valResult, validateWorkflow } = await runValidation(
|
|
3363
|
+
plan,
|
|
3364
|
+
taskSlug,
|
|
3365
|
+
cwd,
|
|
3366
|
+
gateResult,
|
|
3367
|
+
naturalRequest,
|
|
3368
|
+
claimedOutcome,
|
|
3369
|
+
deliveryBaseline,
|
|
3370
|
+
deliveryFinalizer,
|
|
3371
|
+
delegationController,
|
|
3372
|
+
stateMachine
|
|
3373
|
+
);
|
|
2527
3374
|
let result = valResult;
|
|
2528
3375
|
result = await runBoundedRemediation(
|
|
2529
3376
|
plan,
|
|
@@ -2573,10 +3420,26 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
2573
3420
|
}
|
|
2574
3421
|
const finalState = computeFinalState(finalStatus);
|
|
2575
3422
|
stateMachine.transitionTo(finalState);
|
|
3423
|
+
ledger.logEvent({
|
|
3424
|
+
actor: "Atlas",
|
|
3425
|
+
actorType: "control-plane",
|
|
3426
|
+
observed: true,
|
|
3427
|
+
eventType: "finalization_started",
|
|
3428
|
+
provenance: "execute",
|
|
3429
|
+
data: { owner: "execute", status: finalState }
|
|
3430
|
+
});
|
|
2576
3431
|
await generateFinalHandoff(plan, taskSlug, cwd, finalState, result.evidence);
|
|
3432
|
+
ledger.logEvent({
|
|
3433
|
+
actor: "Atlas",
|
|
3434
|
+
actorType: "control-plane",
|
|
3435
|
+
observed: true,
|
|
3436
|
+
eventType: "finalization_completed",
|
|
3437
|
+
provenance: "execute",
|
|
3438
|
+
data: { owner: "execute", status: finalState }
|
|
3439
|
+
});
|
|
2577
3440
|
return { ...result, overallStatus: finalStatus, stateHistory: stateMachine.getHistory() };
|
|
2578
3441
|
} finally {
|
|
2579
|
-
if (plan && !plan.branchNeeded) {
|
|
3442
|
+
if (!branchAuthorized || plan && !plan.branchNeeded) {
|
|
2580
3443
|
console.log(`
|
|
2581
3444
|
[READ-ONLY LEDGER] (In-Memory/Stdout)
|
|
2582
3445
|
${JSON.stringify(ledger.getEvents(), null, 2)}
|
|
@@ -2585,6 +3448,7 @@ ${JSON.stringify(ledger.getEvents(), null, 2)}
|
|
|
2585
3448
|
try {
|
|
2586
3449
|
await ledger.save(`.ai-workflow/history/${taskSlug}-ledger.json`);
|
|
2587
3450
|
} catch (err) {
|
|
3451
|
+
if (plan?.riskLevel === "high") throw err;
|
|
2588
3452
|
}
|
|
2589
3453
|
}
|
|
2590
3454
|
}
|
|
@@ -2621,7 +3485,7 @@ async function runClean({ cwd, yes = false, dryRun = false, purgeAgents = false
|
|
|
2621
3485
|
"opencode"
|
|
2622
3486
|
];
|
|
2623
3487
|
const hasAgents = existsSync(join(cwd, ".agents"));
|
|
2624
|
-
const existingTargets = targets.map((name) => join(cwd, name)).filter((
|
|
3488
|
+
const existingTargets = targets.map((name) => join(cwd, name)).filter((path17) => existsSync(path17) || isSymlink2(path17));
|
|
2625
3489
|
if (existingTargets.length === 0 && (!hasAgents || !purgeAgents)) {
|
|
2626
3490
|
console.log("No AI Workflow configuration or files found in the current project.");
|
|
2627
3491
|
return;
|
|
@@ -2630,7 +3494,7 @@ async function runClean({ cwd, yes = false, dryRun = false, purgeAgents = false
|
|
|
2630
3494
|
console.log("===============================");
|
|
2631
3495
|
if (dryRun) {
|
|
2632
3496
|
console.log("[DRY RUN] The following items would be deleted:");
|
|
2633
|
-
existingTargets.forEach((
|
|
3497
|
+
existingTargets.forEach((path17) => console.log(` - ${path17}`));
|
|
2634
3498
|
if (hasAgents && purgeAgents) {
|
|
2635
3499
|
console.log(` - ${join(cwd, ".agents")} (customizations folder)`);
|
|
2636
3500
|
} else if (hasAgents) {
|
|
@@ -2684,12 +3548,14 @@ function printHelp() {
|
|
|
2684
3548
|
|
|
2685
3549
|
Usage:
|
|
2686
3550
|
ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"]
|
|
2687
|
-
ai-workflow run --spec-path=<path>
|
|
3551
|
+
ai-workflow run --spec-path=<path> [--approval-path=<path>]
|
|
2688
3552
|
ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--claude] [--codex] [--antigravity] [--profile=<profile>]
|
|
2689
3553
|
ai-workflow validate [--a11y] [--visual-dist=<path>] [--port=<port>]
|
|
2690
3554
|
ai-workflow collect-evidence [--task=<slug>] [--evidence-policy=<optional|required>] [--mode=<quick|standard|full>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
|
|
2691
3555
|
ai-workflow doctor
|
|
2692
3556
|
ai-workflow clean [--yes] [--dry-run] [--purge-agents]
|
|
3557
|
+
ai-workflow lint skills
|
|
3558
|
+
ai-workflow skill create <name> [--description="<desc>"] [--dry-run] [--force]
|
|
2693
3559
|
|
|
2694
3560
|
Commands:
|
|
2695
3561
|
execute Orchestrate execution of a natural request through the state machine
|
|
@@ -2698,10 +3564,13 @@ Commands:
|
|
|
2698
3564
|
collect-evidence Run observed project validation; persists EVIDENCE.json under required evidence policy
|
|
2699
3565
|
doctor Verify local ai-workflow installation
|
|
2700
3566
|
clean De-initialize and remove AI Workflow files and symlinks from the project
|
|
3567
|
+
lint Run specialized linters (e.g. lint skills)
|
|
3568
|
+
skill Manage AI workflow skills (e.g. create)
|
|
2701
3569
|
`);
|
|
2702
3570
|
}
|
|
2703
3571
|
function parseFlags(args) {
|
|
2704
3572
|
const specPathArg = args.find((arg) => arg.startsWith("--spec-path="));
|
|
3573
|
+
const approvalPathArg = args.find((arg) => arg.startsWith("--approval-path="));
|
|
2705
3574
|
const profileEqArg = args.find((arg) => arg.startsWith("--profile="));
|
|
2706
3575
|
const taskArg = args.find((arg) => arg.startsWith("--task="));
|
|
2707
3576
|
const modeArg = args.find((arg) => arg.startsWith("--mode="));
|
|
@@ -2722,6 +3591,7 @@ function parseFlags(args) {
|
|
|
2722
3591
|
antigravity: args.includes("--antigravity"),
|
|
2723
3592
|
"dev-mode": args.includes("--dev-mode"),
|
|
2724
3593
|
specPath: specPathArg ? specPathArg.replace("--spec-path=", "") : void 0,
|
|
3594
|
+
approvalPath: approvalPathArg ? approvalPathArg.replace("--approval-path=", "") : void 0,
|
|
2725
3595
|
profile: profileVal || void 0,
|
|
2726
3596
|
taskSlug: taskArg ? taskArg.replace("--task=", "") : void 0,
|
|
2727
3597
|
mode: modeArg ? modeArg.replace("--mode=", "") : void 0,
|
|
@@ -2743,7 +3613,7 @@ var commandMap = {
|
|
|
2743
3613
|
taskSlug: flags.taskSlug
|
|
2744
3614
|
});
|
|
2745
3615
|
if (result && (result.overallStatus === "FAIL_QUALITY_GATE" || result.overallStatus === "BLOCKED" || result.overallStatus === "FAIL")) {
|
|
2746
|
-
process.
|
|
3616
|
+
process.exitCode = result.exitCode === 2 ? 2 : 1;
|
|
2747
3617
|
}
|
|
2748
3618
|
},
|
|
2749
3619
|
run: async (_, flags) => {
|
|
@@ -2773,7 +3643,7 @@ var commandMap = {
|
|
|
2773
3643
|
});
|
|
2774
3644
|
},
|
|
2775
3645
|
validate: async (_, flags) => {
|
|
2776
|
-
const { runValidate } = await import("../validate-
|
|
3646
|
+
const { runValidate } = await import("../validate-F3ZH63LI.js");
|
|
2777
3647
|
await runValidate({
|
|
2778
3648
|
cwd: process.cwd(),
|
|
2779
3649
|
taskSlug: flags.taskSlug,
|
|
@@ -2790,6 +3660,38 @@ var commandMap = {
|
|
|
2790
3660
|
cwd: process.cwd(),
|
|
2791
3661
|
...flags
|
|
2792
3662
|
});
|
|
3663
|
+
},
|
|
3664
|
+
lint: async (args) => {
|
|
3665
|
+
if (args[0] === "skills") {
|
|
3666
|
+
const { runLinter } = await import("../skill-frontmatter-linter-FMJADOK4.js");
|
|
3667
|
+
const { errors, filesScanned } = runLinter(process.cwd());
|
|
3668
|
+
console.log(`Scanned ${filesScanned} skill files.`);
|
|
3669
|
+
if (errors.length > 0) {
|
|
3670
|
+
console.error(`\\nFound ${errors.length} frontmatter issues:`);
|
|
3671
|
+
for (const err of errors) {
|
|
3672
|
+
console.error(`- ${err.filePath}: ${err.message}`);
|
|
3673
|
+
}
|
|
3674
|
+
process.exit(1);
|
|
3675
|
+
} else {
|
|
3676
|
+
console.log("All skills passed frontmatter linting.");
|
|
3677
|
+
}
|
|
3678
|
+
} else {
|
|
3679
|
+
console.error("Unknown lint target. Supported: skills");
|
|
3680
|
+
process.exit(1);
|
|
3681
|
+
}
|
|
3682
|
+
},
|
|
3683
|
+
skill: async (args) => {
|
|
3684
|
+
if (args[0] === "create") {
|
|
3685
|
+
const { parseSkillCreateArgs, runSkillCreate } = await import("../skill-4MEGJ3DO.js");
|
|
3686
|
+
const createOptions = parseSkillCreateArgs(args.slice(1));
|
|
3687
|
+
await runSkillCreate({
|
|
3688
|
+
cwd: process.cwd(),
|
|
3689
|
+
...createOptions
|
|
3690
|
+
});
|
|
3691
|
+
} else {
|
|
3692
|
+
console.error("Unknown skill command. Supported: create");
|
|
3693
|
+
process.exit(1);
|
|
3694
|
+
}
|
|
2793
3695
|
}
|
|
2794
3696
|
};
|
|
2795
3697
|
async function runCli(args) {
|