@williambeto/ai-workflow 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +3 -2
- package/bin/ai-workflow.js +1032 -258
- package/bin/ai-workflow.js.map +1 -1
- package/chunk-AINIR25D.js +120 -0
- package/chunk-AY33SA5W.js +46 -0
- package/{evidence-validator-76ZQQYDU.js → chunk-FNT7DN3N.js} +22 -2
- package/{chunk-W4RTQWVQ.js → chunk-LI76KI7C.js} +977 -820
- package/{chunk-BDZPUAEX.js → chunk-Y4RLP6ZM.js} +11 -3
- package/chunk-YOBY5C72.js +76 -0
- package/core/index.d.ts +32 -1
- package/core/index.js +4 -2
- package/dist-assets/docs/cli-reference.md +47 -2
- package/dist-assets/schemas/approval-receipt.schema.json +35 -0
- package/dist-assets/schemas/evidence.schema.json +33 -0
- 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-IEHLAVZ6.js} +2 -2
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,357 @@ 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-LI76KI7C.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
33
|
ValidationPlanner
|
|
29
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-Y4RLP6ZM.js";
|
|
30
35
|
import "../chunk-XW747GIG.js";
|
|
31
|
-
import
|
|
36
|
+
import {
|
|
37
|
+
createManagedBackup,
|
|
38
|
+
createManagedPathBackup
|
|
39
|
+
} from "../chunk-AY33SA5W.js";
|
|
40
|
+
import {
|
|
41
|
+
BranchGate
|
|
42
|
+
} from "../chunk-AINIR25D.js";
|
|
43
|
+
import {
|
|
44
|
+
__commonJS,
|
|
45
|
+
__toESM
|
|
46
|
+
} from "../chunk-5WRI5ZAA.js";
|
|
47
|
+
|
|
48
|
+
// node_modules/ajv-formats/dist/formats.js
|
|
49
|
+
var require_formats = __commonJS({
|
|
50
|
+
"node_modules/ajv-formats/dist/formats.js"(exports) {
|
|
51
|
+
"use strict";
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.formatNames = exports.fastFormats = exports.fullFormats = void 0;
|
|
54
|
+
function fmtDef(validate, compare) {
|
|
55
|
+
return { validate, compare };
|
|
56
|
+
}
|
|
57
|
+
exports.fullFormats = {
|
|
58
|
+
// date: http://tools.ietf.org/html/rfc3339#section-5.6
|
|
59
|
+
date: fmtDef(date, compareDate),
|
|
60
|
+
// date-time: http://tools.ietf.org/html/rfc3339#section-5.6
|
|
61
|
+
time: fmtDef(getTime(true), compareTime),
|
|
62
|
+
"date-time": fmtDef(getDateTime(true), compareDateTime),
|
|
63
|
+
"iso-time": fmtDef(getTime(), compareIsoTime),
|
|
64
|
+
"iso-date-time": fmtDef(getDateTime(), compareIsoDateTime),
|
|
65
|
+
// duration: https://tools.ietf.org/html/rfc3339#appendix-A
|
|
66
|
+
duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/,
|
|
67
|
+
uri,
|
|
68
|
+
"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,
|
|
69
|
+
// uri-template: https://tools.ietf.org/html/rfc6570
|
|
70
|
+
"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,
|
|
71
|
+
// For the source: https://gist.github.com/dperini/729294
|
|
72
|
+
// For test cases: https://mathiasbynens.be/demo/url-regex
|
|
73
|
+
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,
|
|
74
|
+
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,
|
|
75
|
+
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,
|
|
76
|
+
// optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html
|
|
77
|
+
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)$/,
|
|
78
|
+
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,
|
|
79
|
+
regex,
|
|
80
|
+
// uuid: http://tools.ietf.org/html/rfc4122
|
|
81
|
+
uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,
|
|
82
|
+
// JSON-pointer: https://tools.ietf.org/html/rfc6901
|
|
83
|
+
// uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A
|
|
84
|
+
"json-pointer": /^(?:\/(?:[^~/]|~0|~1)*)*$/,
|
|
85
|
+
"json-pointer-uri-fragment": /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,
|
|
86
|
+
// relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00
|
|
87
|
+
"relative-json-pointer": /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/,
|
|
88
|
+
// the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types
|
|
89
|
+
// byte: https://github.com/miguelmota/is-base64
|
|
90
|
+
byte,
|
|
91
|
+
// signed 32 bit integer
|
|
92
|
+
int32: { type: "number", validate: validateInt32 },
|
|
93
|
+
// signed 64 bit integer
|
|
94
|
+
int64: { type: "number", validate: validateInt64 },
|
|
95
|
+
// C-type float
|
|
96
|
+
float: { type: "number", validate: validateNumber },
|
|
97
|
+
// C-type double
|
|
98
|
+
double: { type: "number", validate: validateNumber },
|
|
99
|
+
// hint to the UI to hide input strings
|
|
100
|
+
password: true,
|
|
101
|
+
// unchecked string payload
|
|
102
|
+
binary: true
|
|
103
|
+
};
|
|
104
|
+
exports.fastFormats = {
|
|
105
|
+
...exports.fullFormats,
|
|
106
|
+
date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate),
|
|
107
|
+
time: fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareTime),
|
|
108
|
+
"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),
|
|
109
|
+
"iso-time": fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoTime),
|
|
110
|
+
"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),
|
|
111
|
+
// uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js
|
|
112
|
+
uri: /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i,
|
|
113
|
+
"uri-reference": /^(?:(?:[a-z][a-z0-9+\-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i,
|
|
114
|
+
// email (sources from jsen validator):
|
|
115
|
+
// http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
|
|
116
|
+
// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation')
|
|
117
|
+
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
|
|
118
|
+
};
|
|
119
|
+
exports.formatNames = Object.keys(exports.fullFormats);
|
|
120
|
+
function isLeapYear(year) {
|
|
121
|
+
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
122
|
+
}
|
|
123
|
+
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
124
|
+
var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
125
|
+
function date(str) {
|
|
126
|
+
const matches = DATE.exec(str);
|
|
127
|
+
if (!matches)
|
|
128
|
+
return false;
|
|
129
|
+
const year = +matches[1];
|
|
130
|
+
const month = +matches[2];
|
|
131
|
+
const day = +matches[3];
|
|
132
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
|
|
133
|
+
}
|
|
134
|
+
function compareDate(d1, d2) {
|
|
135
|
+
if (!(d1 && d2))
|
|
136
|
+
return void 0;
|
|
137
|
+
if (d1 > d2)
|
|
138
|
+
return 1;
|
|
139
|
+
if (d1 < d2)
|
|
140
|
+
return -1;
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
144
|
+
function getTime(strictTimeZone) {
|
|
145
|
+
return function time(str) {
|
|
146
|
+
const matches = TIME.exec(str);
|
|
147
|
+
if (!matches)
|
|
148
|
+
return false;
|
|
149
|
+
const hr = +matches[1];
|
|
150
|
+
const min = +matches[2];
|
|
151
|
+
const sec = +matches[3];
|
|
152
|
+
const tz = matches[4];
|
|
153
|
+
const tzSign = matches[5] === "-" ? -1 : 1;
|
|
154
|
+
const tzH = +(matches[6] || 0);
|
|
155
|
+
const tzM = +(matches[7] || 0);
|
|
156
|
+
if (tzH > 23 || tzM > 59 || strictTimeZone && !tz)
|
|
157
|
+
return false;
|
|
158
|
+
if (hr <= 23 && min <= 59 && sec < 60)
|
|
159
|
+
return true;
|
|
160
|
+
const utcMin = min - tzM * tzSign;
|
|
161
|
+
const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0);
|
|
162
|
+
return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function compareTime(s1, s2) {
|
|
166
|
+
if (!(s1 && s2))
|
|
167
|
+
return void 0;
|
|
168
|
+
const t1 = (/* @__PURE__ */ new Date("2020-01-01T" + s1)).valueOf();
|
|
169
|
+
const t2 = (/* @__PURE__ */ new Date("2020-01-01T" + s2)).valueOf();
|
|
170
|
+
if (!(t1 && t2))
|
|
171
|
+
return void 0;
|
|
172
|
+
return t1 - t2;
|
|
173
|
+
}
|
|
174
|
+
function compareIsoTime(t1, t2) {
|
|
175
|
+
if (!(t1 && t2))
|
|
176
|
+
return void 0;
|
|
177
|
+
const a1 = TIME.exec(t1);
|
|
178
|
+
const a2 = TIME.exec(t2);
|
|
179
|
+
if (!(a1 && a2))
|
|
180
|
+
return void 0;
|
|
181
|
+
t1 = a1[1] + a1[2] + a1[3];
|
|
182
|
+
t2 = a2[1] + a2[2] + a2[3];
|
|
183
|
+
if (t1 > t2)
|
|
184
|
+
return 1;
|
|
185
|
+
if (t1 < t2)
|
|
186
|
+
return -1;
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
var DATE_TIME_SEPARATOR = /t|\s/i;
|
|
190
|
+
function getDateTime(strictTimeZone) {
|
|
191
|
+
const time = getTime(strictTimeZone);
|
|
192
|
+
return function date_time(str) {
|
|
193
|
+
const dateTime = str.split(DATE_TIME_SEPARATOR);
|
|
194
|
+
return dateTime.length === 2 && date(dateTime[0]) && time(dateTime[1]);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function compareDateTime(dt1, dt2) {
|
|
198
|
+
if (!(dt1 && dt2))
|
|
199
|
+
return void 0;
|
|
200
|
+
const d1 = new Date(dt1).valueOf();
|
|
201
|
+
const d2 = new Date(dt2).valueOf();
|
|
202
|
+
if (!(d1 && d2))
|
|
203
|
+
return void 0;
|
|
204
|
+
return d1 - d2;
|
|
205
|
+
}
|
|
206
|
+
function compareIsoDateTime(dt1, dt2) {
|
|
207
|
+
if (!(dt1 && dt2))
|
|
208
|
+
return void 0;
|
|
209
|
+
const [d1, t1] = dt1.split(DATE_TIME_SEPARATOR);
|
|
210
|
+
const [d2, t2] = dt2.split(DATE_TIME_SEPARATOR);
|
|
211
|
+
const res = compareDate(d1, d2);
|
|
212
|
+
if (res === void 0)
|
|
213
|
+
return void 0;
|
|
214
|
+
return res || compareTime(t1, t2);
|
|
215
|
+
}
|
|
216
|
+
var NOT_URI_FRAGMENT = /\/|:/;
|
|
217
|
+
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;
|
|
218
|
+
function uri(str) {
|
|
219
|
+
return NOT_URI_FRAGMENT.test(str) && URI.test(str);
|
|
220
|
+
}
|
|
221
|
+
var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
222
|
+
function byte(str) {
|
|
223
|
+
BYTE.lastIndex = 0;
|
|
224
|
+
return BYTE.test(str);
|
|
225
|
+
}
|
|
226
|
+
var MIN_INT32 = -(2 ** 31);
|
|
227
|
+
var MAX_INT32 = 2 ** 31 - 1;
|
|
228
|
+
function validateInt32(value) {
|
|
229
|
+
return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32;
|
|
230
|
+
}
|
|
231
|
+
function validateInt64(value) {
|
|
232
|
+
return Number.isInteger(value);
|
|
233
|
+
}
|
|
234
|
+
function validateNumber() {
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
var Z_ANCHOR = /[^\\]\\Z/;
|
|
238
|
+
function regex(str) {
|
|
239
|
+
if (Z_ANCHOR.test(str))
|
|
240
|
+
return false;
|
|
241
|
+
try {
|
|
242
|
+
new RegExp(str);
|
|
243
|
+
return true;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// node_modules/ajv-formats/dist/limit.js
|
|
252
|
+
var require_limit = __commonJS({
|
|
253
|
+
"node_modules/ajv-formats/dist/limit.js"(exports) {
|
|
254
|
+
"use strict";
|
|
255
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
256
|
+
exports.formatLimitDefinition = void 0;
|
|
257
|
+
var ajv_1 = require_ajv();
|
|
258
|
+
var codegen_1 = require_codegen();
|
|
259
|
+
var ops = codegen_1.operators;
|
|
260
|
+
var KWDs = {
|
|
261
|
+
formatMaximum: { okStr: "<=", ok: ops.LTE, fail: ops.GT },
|
|
262
|
+
formatMinimum: { okStr: ">=", ok: ops.GTE, fail: ops.LT },
|
|
263
|
+
formatExclusiveMaximum: { okStr: "<", ok: ops.LT, fail: ops.GTE },
|
|
264
|
+
formatExclusiveMinimum: { okStr: ">", ok: ops.GT, fail: ops.LTE }
|
|
265
|
+
};
|
|
266
|
+
var error = {
|
|
267
|
+
message: ({ keyword, schemaCode }) => (0, codegen_1.str)`should be ${KWDs[keyword].okStr} ${schemaCode}`,
|
|
268
|
+
params: ({ keyword, schemaCode }) => (0, codegen_1._)`{comparison: ${KWDs[keyword].okStr}, limit: ${schemaCode}}`
|
|
269
|
+
};
|
|
270
|
+
exports.formatLimitDefinition = {
|
|
271
|
+
keyword: Object.keys(KWDs),
|
|
272
|
+
type: "string",
|
|
273
|
+
schemaType: "string",
|
|
274
|
+
$data: true,
|
|
275
|
+
error,
|
|
276
|
+
code(cxt) {
|
|
277
|
+
const { gen, data, schemaCode, keyword, it } = cxt;
|
|
278
|
+
const { opts, self } = it;
|
|
279
|
+
if (!opts.validateFormats)
|
|
280
|
+
return;
|
|
281
|
+
const fCxt = new ajv_1.KeywordCxt(it, self.RULES.all.format.definition, "format");
|
|
282
|
+
if (fCxt.$data)
|
|
283
|
+
validate$DataFormat();
|
|
284
|
+
else
|
|
285
|
+
validateFormat();
|
|
286
|
+
function validate$DataFormat() {
|
|
287
|
+
const fmts = gen.scopeValue("formats", {
|
|
288
|
+
ref: self.formats,
|
|
289
|
+
code: opts.code.formats
|
|
290
|
+
});
|
|
291
|
+
const fmt = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
|
|
292
|
+
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)));
|
|
293
|
+
}
|
|
294
|
+
function validateFormat() {
|
|
295
|
+
const format = fCxt.schema;
|
|
296
|
+
const fmtDef = self.formats[format];
|
|
297
|
+
if (!fmtDef || fmtDef === true)
|
|
298
|
+
return;
|
|
299
|
+
if (typeof fmtDef != "object" || fmtDef instanceof RegExp || typeof fmtDef.compare != "function") {
|
|
300
|
+
throw new Error(`"${keyword}": format "${format}" does not define "compare" function`);
|
|
301
|
+
}
|
|
302
|
+
const fmt = gen.scopeValue("formats", {
|
|
303
|
+
key: format,
|
|
304
|
+
ref: fmtDef,
|
|
305
|
+
code: opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(format)}` : void 0
|
|
306
|
+
});
|
|
307
|
+
cxt.fail$data(compareCode(fmt));
|
|
308
|
+
}
|
|
309
|
+
function compareCode(fmt) {
|
|
310
|
+
return (0, codegen_1._)`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
dependencies: ["format"]
|
|
314
|
+
};
|
|
315
|
+
var formatLimitPlugin = (ajv) => {
|
|
316
|
+
ajv.addKeyword(exports.formatLimitDefinition);
|
|
317
|
+
return ajv;
|
|
318
|
+
};
|
|
319
|
+
exports.default = formatLimitPlugin;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// node_modules/ajv-formats/dist/index.js
|
|
324
|
+
var require_dist = __commonJS({
|
|
325
|
+
"node_modules/ajv-formats/dist/index.js"(exports, module) {
|
|
326
|
+
"use strict";
|
|
327
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
328
|
+
var formats_1 = require_formats();
|
|
329
|
+
var limit_1 = require_limit();
|
|
330
|
+
var codegen_1 = require_codegen();
|
|
331
|
+
var fullName = new codegen_1.Name("fullFormats");
|
|
332
|
+
var fastName = new codegen_1.Name("fastFormats");
|
|
333
|
+
var formatsPlugin = (ajv, opts = { keywords: true }) => {
|
|
334
|
+
if (Array.isArray(opts)) {
|
|
335
|
+
addFormats2(ajv, opts, formats_1.fullFormats, fullName);
|
|
336
|
+
return ajv;
|
|
337
|
+
}
|
|
338
|
+
const [formats, exportName] = opts.mode === "fast" ? [formats_1.fastFormats, fastName] : [formats_1.fullFormats, fullName];
|
|
339
|
+
const list = opts.formats || formats_1.formatNames;
|
|
340
|
+
addFormats2(ajv, list, formats, exportName);
|
|
341
|
+
if (opts.keywords)
|
|
342
|
+
(0, limit_1.default)(ajv);
|
|
343
|
+
return ajv;
|
|
344
|
+
};
|
|
345
|
+
formatsPlugin.get = (name, mode = "full") => {
|
|
346
|
+
const formats = mode === "fast" ? formats_1.fastFormats : formats_1.fullFormats;
|
|
347
|
+
const f = formats[name];
|
|
348
|
+
if (!f)
|
|
349
|
+
throw new Error(`Unknown format "${name}"`);
|
|
350
|
+
return f;
|
|
351
|
+
};
|
|
352
|
+
function addFormats2(ajv, list, fs15, exportName) {
|
|
353
|
+
var _a;
|
|
354
|
+
var _b;
|
|
355
|
+
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
356
|
+
for (const f of list)
|
|
357
|
+
ajv.addFormat(f, fs15[f]);
|
|
358
|
+
}
|
|
359
|
+
module.exports = exports = formatsPlugin;
|
|
360
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
361
|
+
exports.default = formatsPlugin;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
32
364
|
|
|
33
365
|
// src/cli/commands/init.ts
|
|
34
|
-
import
|
|
35
|
-
import
|
|
366
|
+
import fs6 from "fs/promises";
|
|
367
|
+
import path8 from "path";
|
|
36
368
|
|
|
37
369
|
// src/core/install-plan.ts
|
|
38
370
|
import path from "path";
|
|
@@ -390,49 +722,8 @@ async function readJsonc(filePath) {
|
|
|
390
722
|
return JSON.parse(stripTrailingCommas(stripJsonComments(content)));
|
|
391
723
|
}
|
|
392
724
|
|
|
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
725
|
// src/core/opencode-merge.ts
|
|
435
|
-
import
|
|
726
|
+
import path3 from "path";
|
|
436
727
|
var LEGACY_MARKER = "_aiWorkflowManaged";
|
|
437
728
|
var AGENTS = {
|
|
438
729
|
Atlas: { description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
@@ -515,7 +806,7 @@ function buildManagedConfig() {
|
|
|
515
806
|
};
|
|
516
807
|
}
|
|
517
808
|
async function mergeOpencodeConfig(cwd, { force = false, backupRoot = ".ai-workflow-backups" } = {}) {
|
|
518
|
-
const targetPath =
|
|
809
|
+
const targetPath = path3.join(cwd, "opencode.jsonc");
|
|
519
810
|
const managed = buildManagedConfig();
|
|
520
811
|
if (!await exists(targetPath)) {
|
|
521
812
|
await writeFileSafe(targetPath, `${JSON.stringify(managed, null, 2)}
|
|
@@ -567,14 +858,15 @@ async function mergeOpencodeConfig(cwd, { force = false, backupRoot = ".ai-workf
|
|
|
567
858
|
}
|
|
568
859
|
|
|
569
860
|
// src/core/symlink-layout.ts
|
|
570
|
-
import
|
|
571
|
-
import
|
|
861
|
+
import fs2 from "fs/promises";
|
|
862
|
+
import path4 from "path";
|
|
572
863
|
function normalize(p) {
|
|
573
|
-
return p.split(
|
|
864
|
+
return p.split(path4.sep).join("/");
|
|
574
865
|
}
|
|
575
866
|
var LINKABLE_ROOTS = /* @__PURE__ */ new Set([
|
|
576
867
|
"opencode",
|
|
577
|
-
".agents"
|
|
868
|
+
".agents",
|
|
869
|
+
"examples"
|
|
578
870
|
]);
|
|
579
871
|
var LINKABLE_FILES = /* @__PURE__ */ new Set([]);
|
|
580
872
|
function buildSymlinkEntries({ templateFiles, installRoot = ".ai-workflow" }) {
|
|
@@ -597,38 +889,38 @@ function buildSymlinkEntries({ templateFiles, installRoot = ".ai-workflow" }) {
|
|
|
597
889
|
}
|
|
598
890
|
async function isSymlinkTo(absoluteLinkPath, absoluteTargetPath) {
|
|
599
891
|
try {
|
|
600
|
-
const stat = await
|
|
892
|
+
const stat = await fs2.lstat(absoluteLinkPath);
|
|
601
893
|
if (!stat.isSymbolicLink()) return false;
|
|
602
|
-
const currentTarget = await
|
|
603
|
-
const resolved =
|
|
894
|
+
const currentTarget = await fs2.readlink(absoluteLinkPath);
|
|
895
|
+
const resolved = path4.resolve(path4.dirname(absoluteLinkPath), currentTarget);
|
|
604
896
|
return resolved === absoluteTargetPath;
|
|
605
897
|
} catch {
|
|
606
898
|
return false;
|
|
607
899
|
}
|
|
608
900
|
}
|
|
609
901
|
async function setupInternalSymlinks(cwd, installRoot = ".ai-workflow") {
|
|
610
|
-
const skillsLink =
|
|
611
|
-
const skillsTarget =
|
|
902
|
+
const skillsLink = path4.join(cwd, installRoot, "opencode/skills");
|
|
903
|
+
const skillsTarget = path4.join(cwd, installRoot, ".agents/skills");
|
|
612
904
|
if (!await exists2(skillsTarget)) return;
|
|
613
905
|
try {
|
|
614
|
-
const stat = await
|
|
906
|
+
const stat = await fs2.lstat(skillsLink).catch(() => null);
|
|
615
907
|
if (stat) {
|
|
616
908
|
if (stat.isSymbolicLink()) {
|
|
617
|
-
const current = await
|
|
909
|
+
const current = await fs2.readlink(skillsLink);
|
|
618
910
|
if (current === "../.agents/skills") return;
|
|
619
|
-
await
|
|
911
|
+
await fs2.unlink(skillsLink);
|
|
620
912
|
} else {
|
|
621
913
|
return;
|
|
622
914
|
}
|
|
623
915
|
}
|
|
624
916
|
const type = process.platform === "win32" ? "junction" : "dir";
|
|
625
|
-
await
|
|
917
|
+
await fs2.symlink("../.agents/skills", skillsLink, type);
|
|
626
918
|
} catch {
|
|
627
919
|
}
|
|
628
920
|
}
|
|
629
921
|
async function exists2(p) {
|
|
630
922
|
try {
|
|
631
|
-
await
|
|
923
|
+
await fs2.access(p);
|
|
632
924
|
return true;
|
|
633
925
|
} catch {
|
|
634
926
|
return false;
|
|
@@ -636,8 +928,8 @@ async function exists2(p) {
|
|
|
636
928
|
}
|
|
637
929
|
|
|
638
930
|
// src/adapters/platforms/claude.ts
|
|
639
|
-
import
|
|
640
|
-
import
|
|
931
|
+
import fs3 from "fs/promises";
|
|
932
|
+
import path5 from "path";
|
|
641
933
|
var ClaudeAdapter = class {
|
|
642
934
|
cwd;
|
|
643
935
|
constructor({ cwd }) {
|
|
@@ -647,8 +939,8 @@ var ClaudeAdapter = class {
|
|
|
647
939
|
* Transforms an OpenCode agent or skill into a Claude rule with completion contract.
|
|
648
940
|
*/
|
|
649
941
|
async transformToRule(filePath, type = "agent") {
|
|
650
|
-
const content = await
|
|
651
|
-
const fileName =
|
|
942
|
+
const content = await fs3.readFile(filePath, "utf8");
|
|
943
|
+
const fileName = path5.basename(filePath, ".md") === "SKILL" ? path5.basename(path5.dirname(filePath)) : path5.basename(filePath, ".md");
|
|
652
944
|
const ruleName = `${type}-${fileName.toLowerCase()}`;
|
|
653
945
|
const hardenedContent = `---
|
|
654
946
|
name: ${ruleName}
|
|
@@ -678,34 +970,34 @@ Every task completion MUST provide the following payload:
|
|
|
678
970
|
* Deploys agents, skills, and root CLAUDE.md.
|
|
679
971
|
*/
|
|
680
972
|
async deploy(installRoot = ".ai-workflow") {
|
|
681
|
-
const rulesDir =
|
|
682
|
-
await
|
|
683
|
-
const sourceAgentsDir =
|
|
684
|
-
const sourceSkillsDir =
|
|
685
|
-
const sourceCommandsDir =
|
|
686
|
-
const agentFiles = await
|
|
973
|
+
const rulesDir = path5.join(this.cwd, ".claude/rules");
|
|
974
|
+
await fs3.mkdir(rulesDir, { recursive: true });
|
|
975
|
+
const sourceAgentsDir = path5.join(this.cwd, installRoot, "opencode/agents");
|
|
976
|
+
const sourceSkillsDir = path5.join(this.cwd, installRoot, "opencode/skills");
|
|
977
|
+
const sourceCommandsDir = path5.join(this.cwd, installRoot, "opencode/commands");
|
|
978
|
+
const agentFiles = await fs3.readdir(sourceAgentsDir).catch(() => []);
|
|
687
979
|
for (const file of agentFiles) {
|
|
688
980
|
if (!file.endsWith(".md")) continue;
|
|
689
|
-
const transformed = await this.transformToRule(
|
|
690
|
-
await
|
|
981
|
+
const transformed = await this.transformToRule(path5.join(sourceAgentsDir, file), "agent");
|
|
982
|
+
await fs3.writeFile(path5.join(rulesDir, `${transformed.name}.md`), transformed.content);
|
|
691
983
|
}
|
|
692
|
-
const skillFolders = await
|
|
984
|
+
const skillFolders = await fs3.readdir(sourceSkillsDir).catch(() => []);
|
|
693
985
|
for (const folder of skillFolders) {
|
|
694
|
-
const skillSource =
|
|
986
|
+
const skillSource = path5.join(sourceSkillsDir, folder, "SKILL.md");
|
|
695
987
|
if (await this.exists(skillSource)) {
|
|
696
988
|
const transformed = await this.transformToRule(skillSource, "skill");
|
|
697
|
-
await
|
|
989
|
+
await fs3.writeFile(path5.join(rulesDir, `${transformed.name}.md`), transformed.content);
|
|
698
990
|
}
|
|
699
991
|
}
|
|
700
992
|
await this.deployRootClaudeMd(sourceCommandsDir);
|
|
701
993
|
}
|
|
702
994
|
async deployRootClaudeMd(sourceCommandsDir) {
|
|
703
|
-
const targetPath =
|
|
704
|
-
const commandFiles = await
|
|
995
|
+
const targetPath = path5.join(this.cwd, "CLAUDE.md");
|
|
996
|
+
const commandFiles = await fs3.readdir(sourceCommandsDir).catch(() => []);
|
|
705
997
|
let commandsDoc = "";
|
|
706
998
|
for (const file of commandFiles) {
|
|
707
999
|
if (!file.endsWith(".md")) continue;
|
|
708
|
-
const name =
|
|
1000
|
+
const name = path5.basename(file, ".md");
|
|
709
1001
|
commandsDoc += `- \`ai-workflow run --spec-path=...\` (Target: ${name})
|
|
710
1002
|
`;
|
|
711
1003
|
}
|
|
@@ -729,11 +1021,11 @@ Adhere to the specific rules and personas defined in \`.claude/rules/\`.
|
|
|
729
1021
|
## Completion Contract
|
|
730
1022
|
Every task completion MUST follow the payload format defined in the individual rule files.
|
|
731
1023
|
`;
|
|
732
|
-
await
|
|
1024
|
+
await fs3.writeFile(targetPath, instructions);
|
|
733
1025
|
}
|
|
734
1026
|
async exists(p) {
|
|
735
1027
|
try {
|
|
736
|
-
await
|
|
1028
|
+
await fs3.access(p);
|
|
737
1029
|
return true;
|
|
738
1030
|
} catch {
|
|
739
1031
|
return false;
|
|
@@ -742,8 +1034,8 @@ Every task completion MUST follow the payload format defined in the individual r
|
|
|
742
1034
|
};
|
|
743
1035
|
|
|
744
1036
|
// src/adapters/platforms/codex.ts
|
|
745
|
-
import
|
|
746
|
-
import
|
|
1037
|
+
import fs4 from "fs/promises";
|
|
1038
|
+
import path6 from "path";
|
|
747
1039
|
var CodexAdapter = class {
|
|
748
1040
|
cwd;
|
|
749
1041
|
constructor({ cwd }) {
|
|
@@ -753,10 +1045,15 @@ var CodexAdapter = class {
|
|
|
753
1045
|
* Transforms an OpenCode agent into a Codex prompt with completion contract.
|
|
754
1046
|
*/
|
|
755
1047
|
async transformAgent(filePath) {
|
|
756
|
-
const content = await
|
|
757
|
-
const fileName =
|
|
1048
|
+
const content = await fs4.readFile(filePath, "utf8");
|
|
1049
|
+
const fileName = path6.basename(filePath, ".md");
|
|
758
1050
|
const hardenedContent = `${content}
|
|
759
1051
|
|
|
1052
|
+
## System Directives for Codex
|
|
1053
|
+
- **No Conversational Filler**: Output only the requested artifact or analysis. Do not apologize or add filler text.
|
|
1054
|
+
- **Explicit Paths**: Always specify the exact, absolute or relative file path before making edits.
|
|
1055
|
+
- **Read-Only First**: Do not invent or assume file contents. Always read the workspace first.
|
|
1056
|
+
|
|
760
1057
|
## Completion Contract (Mandatory)
|
|
761
1058
|
Every task completion MUST provide the following payload:
|
|
762
1059
|
1. **Status**: ${COMPLETION_STATUS_TEXT}
|
|
@@ -781,55 +1078,55 @@ Every task completion MUST provide the following payload:
|
|
|
781
1078
|
* - Commands -> .codex/prompts/
|
|
782
1079
|
*/
|
|
783
1080
|
async deploy(installRoot = ".ai-workflow", force) {
|
|
784
|
-
const sourceAgentsDir =
|
|
785
|
-
const sourceSkillsDir =
|
|
786
|
-
const sourceCommandsDir =
|
|
787
|
-
const codexAgentsDir =
|
|
788
|
-
await
|
|
789
|
-
const agentFiles = await
|
|
1081
|
+
const sourceAgentsDir = path6.join(this.cwd, installRoot, "opencode/agents");
|
|
1082
|
+
const sourceSkillsDir = path6.join(this.cwd, installRoot, "opencode/skills");
|
|
1083
|
+
const sourceCommandsDir = path6.join(this.cwd, installRoot, "opencode/commands");
|
|
1084
|
+
const codexAgentsDir = path6.join(this.cwd, ".github/agents");
|
|
1085
|
+
await fs4.mkdir(codexAgentsDir, { recursive: true });
|
|
1086
|
+
const agentFiles = await fs4.readdir(sourceAgentsDir).catch(() => []);
|
|
790
1087
|
for (const file of agentFiles) {
|
|
791
1088
|
if (!file.endsWith(".md")) continue;
|
|
792
|
-
const transformed = await this.transformAgent(
|
|
793
|
-
await
|
|
1089
|
+
const transformed = await this.transformAgent(path6.join(sourceAgentsDir, file));
|
|
1090
|
+
await fs4.writeFile(path6.join(codexAgentsDir, `${transformed.name}.md`), transformed.content);
|
|
794
1091
|
}
|
|
795
|
-
const codexSkillsDir =
|
|
796
|
-
await
|
|
797
|
-
const skillFolders = await
|
|
1092
|
+
const codexSkillsDir = path6.join(this.cwd, ".codex/skills");
|
|
1093
|
+
await fs4.mkdir(codexSkillsDir, { recursive: true });
|
|
1094
|
+
const skillFolders = await fs4.readdir(sourceSkillsDir).catch(() => []);
|
|
798
1095
|
for (const folder of skillFolders) {
|
|
799
|
-
const skillSource =
|
|
1096
|
+
const skillSource = path6.join(sourceSkillsDir, folder, "SKILL.md");
|
|
800
1097
|
if (await this.exists(skillSource)) {
|
|
801
|
-
const targetDir =
|
|
802
|
-
await
|
|
803
|
-
const content = await
|
|
804
|
-
await
|
|
1098
|
+
const targetDir = path6.join(codexSkillsDir, folder);
|
|
1099
|
+
await fs4.mkdir(targetDir, { recursive: true });
|
|
1100
|
+
const content = await fs4.readFile(skillSource, "utf8");
|
|
1101
|
+
await fs4.writeFile(path6.join(targetDir, "SKILL.md"), content);
|
|
805
1102
|
}
|
|
806
1103
|
}
|
|
807
|
-
const codexPromptsDir =
|
|
808
|
-
await
|
|
809
|
-
const commandFiles = await
|
|
1104
|
+
const codexPromptsDir = path6.join(this.cwd, ".codex/prompts");
|
|
1105
|
+
await fs4.mkdir(codexPromptsDir, { recursive: true });
|
|
1106
|
+
const commandFiles = await fs4.readdir(sourceCommandsDir).catch(() => []);
|
|
810
1107
|
for (const file of commandFiles) {
|
|
811
1108
|
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
812
|
-
const targetFileName = file.endsWith(".toml") ? `${
|
|
813
|
-
const content = await
|
|
814
|
-
await
|
|
815
|
-
}
|
|
816
|
-
const codexPoliciesDir =
|
|
817
|
-
await
|
|
818
|
-
const sourcePoliciesDir =
|
|
819
|
-
const policyFiles = await
|
|
1109
|
+
const targetFileName = file.endsWith(".toml") ? `${path6.basename(file, ".toml")}.md` : file;
|
|
1110
|
+
const content = await fs4.readFile(path6.join(sourceCommandsDir, file), "utf8");
|
|
1111
|
+
await fs4.writeFile(path6.join(codexPromptsDir, targetFileName), content);
|
|
1112
|
+
}
|
|
1113
|
+
const codexPoliciesDir = path6.join(this.cwd, ".codex", "docs", "policies");
|
|
1114
|
+
await fs4.mkdir(codexPoliciesDir, { recursive: true });
|
|
1115
|
+
const sourcePoliciesDir = path6.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
1116
|
+
const policyFiles = await fs4.readdir(sourcePoliciesDir).catch(() => []);
|
|
820
1117
|
for (const file of policyFiles) {
|
|
821
1118
|
if (!file.endsWith(".md")) continue;
|
|
822
|
-
const policyContent = await
|
|
823
|
-
await
|
|
1119
|
+
const policyContent = await fs4.readFile(path6.join(sourcePoliciesDir, file), "utf8");
|
|
1120
|
+
await fs4.writeFile(path6.join(codexPoliciesDir, file), policyContent);
|
|
824
1121
|
}
|
|
825
1122
|
await this.deployRootCopilotInstructions();
|
|
826
1123
|
await this.deployRootCodexMd(force);
|
|
827
1124
|
await this.deployRootAgentsMd(force);
|
|
828
1125
|
}
|
|
829
1126
|
async deployRootCopilotInstructions() {
|
|
830
|
-
const githubDir =
|
|
831
|
-
await
|
|
832
|
-
const targetPath =
|
|
1127
|
+
const githubDir = path6.join(this.cwd, ".github");
|
|
1128
|
+
await fs4.mkdir(githubDir, { recursive: true });
|
|
1129
|
+
const targetPath = path6.join(githubDir, "copilot-instructions.md");
|
|
833
1130
|
const instructions = `# AI Workflow Kit Governance
|
|
834
1131
|
|
|
835
1132
|
This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
|
|
@@ -843,18 +1140,23 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
843
1140
|
- When acting as an agent, consult the instructions in \`.github/agents/\`.
|
|
844
1141
|
- When utilizing skills, consult the files in \`.codex/skills/\`.
|
|
845
1142
|
|
|
1143
|
+
## System Boundaries
|
|
1144
|
+
- **No Conversational Filler**: Do not apologize or add filler text.
|
|
1145
|
+
- **Strict Evidence**: You must provide the Completion Contract payload when finishing a task.
|
|
1146
|
+
- **Immutable Core**: Do not modify files inside \`.ai-workflow/\`, \`.agents/\`, \`.claude/\`, or \`.codex/\` unless explicitly requested.
|
|
1147
|
+
|
|
846
1148
|
## Completion Contract
|
|
847
1149
|
Every task completion MUST follow the payload format defined in the individual agent files.
|
|
848
1150
|
`;
|
|
849
|
-
await
|
|
1151
|
+
await fs4.writeFile(targetPath, instructions);
|
|
850
1152
|
}
|
|
851
1153
|
async deployRootCodexMd(force) {
|
|
852
|
-
const targetPath =
|
|
1154
|
+
const targetPath = path6.join(this.cwd, "CODEX.md");
|
|
853
1155
|
if (!force && await this.exists(targetPath)) return;
|
|
854
|
-
const templatePath =
|
|
1156
|
+
const templatePath = path6.join(this.cwd, ".ai-workflow", "templates", "CODEX.md.template");
|
|
855
1157
|
if (await this.exists(templatePath)) {
|
|
856
|
-
const content = await
|
|
857
|
-
await
|
|
1158
|
+
const content = await fs4.readFile(templatePath, "utf8");
|
|
1159
|
+
await fs4.writeFile(targetPath, content);
|
|
858
1160
|
return;
|
|
859
1161
|
}
|
|
860
1162
|
const instructions = `# AI Workflow Kit Governance
|
|
@@ -870,26 +1172,31 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
870
1172
|
- When acting as an agent, consult the instructions in \`.github/agents/\`.
|
|
871
1173
|
- When utilizing skills, consult the files in \`.codex/skills/\`.
|
|
872
1174
|
|
|
1175
|
+
## System Boundaries
|
|
1176
|
+
- **No Conversational Filler**: Do not apologize or add filler text.
|
|
1177
|
+
- **Strict Evidence**: You must provide the Completion Contract payload when finishing a task.
|
|
1178
|
+
- **Immutable Core**: Do not modify files inside \`.ai-workflow/\`, \`.agents/\`, \`.claude/\`, or \`.codex/\` unless explicitly requested.
|
|
1179
|
+
|
|
873
1180
|
## Completion Contract
|
|
874
1181
|
Every task completion MUST follow the payload format defined in the individual agent files.
|
|
875
1182
|
`;
|
|
876
|
-
await
|
|
1183
|
+
await fs4.writeFile(targetPath, instructions);
|
|
877
1184
|
}
|
|
878
1185
|
async deployRootAgentsMd(force) {
|
|
879
|
-
const targetPath =
|
|
1186
|
+
const targetPath = path6.join(this.cwd, "AGENTS.md");
|
|
880
1187
|
if (!force && await this.exists(targetPath)) return;
|
|
881
|
-
const templatePath =
|
|
1188
|
+
const templatePath = path6.join(this.cwd, ".ai-workflow", "AGENTS.md");
|
|
882
1189
|
if (await this.exists(templatePath)) {
|
|
883
|
-
let content = await
|
|
1190
|
+
let content = await fs4.readFile(templatePath, "utf8");
|
|
884
1191
|
content = content.replace(/\.agents\/skills/g, ".codex/skills");
|
|
885
1192
|
content = content.replace(/opencode\/commands/g, ".codex/prompts");
|
|
886
1193
|
content = content.replace(/opencode\/docs\/policies/g, ".codex/docs/policies");
|
|
887
|
-
await
|
|
1194
|
+
await fs4.writeFile(targetPath, content);
|
|
888
1195
|
}
|
|
889
1196
|
}
|
|
890
1197
|
async exists(p) {
|
|
891
1198
|
try {
|
|
892
|
-
await
|
|
1199
|
+
await fs4.access(p);
|
|
893
1200
|
return true;
|
|
894
1201
|
} catch {
|
|
895
1202
|
return false;
|
|
@@ -898,8 +1205,8 @@ Every task completion MUST follow the payload format defined in the individual a
|
|
|
898
1205
|
};
|
|
899
1206
|
|
|
900
1207
|
// src/adapters/platforms/antigravity.ts
|
|
901
|
-
import
|
|
902
|
-
import
|
|
1208
|
+
import fs5 from "fs/promises";
|
|
1209
|
+
import path7 from "path";
|
|
903
1210
|
import { pathToFileURL } from "url";
|
|
904
1211
|
var PERSONA_MAPPING = {
|
|
905
1212
|
"atlas": "Atlas",
|
|
@@ -922,8 +1229,8 @@ var AntigravityAdapter = class {
|
|
|
922
1229
|
* Transforms an OpenCode agent or specialized skill file to an Antigravity Skill.
|
|
923
1230
|
*/
|
|
924
1231
|
async transform(filePath) {
|
|
925
|
-
const content = await
|
|
926
|
-
const fileName =
|
|
1232
|
+
const content = await fs5.readFile(filePath, "utf8");
|
|
1233
|
+
const fileName = path7.basename(filePath, ".md") === "SKILL" ? path7.basename(path7.dirname(filePath)) : path7.basename(filePath, ".md");
|
|
927
1234
|
const persona = PERSONA_MAPPING[fileName.toLowerCase()] || "Astra";
|
|
928
1235
|
if (content.trim().startsWith("---")) {
|
|
929
1236
|
return { content, name: fileName };
|
|
@@ -959,17 +1266,17 @@ Every task completion MUST provide the following payload:
|
|
|
959
1266
|
* Also orchestrates the .antigravity/ native integration.
|
|
960
1267
|
*/
|
|
961
1268
|
async deploy(transformedItems, installRoot = ".ai-workflow") {
|
|
962
|
-
const agentsDir =
|
|
963
|
-
const skillsDir =
|
|
964
|
-
await
|
|
965
|
-
await
|
|
1269
|
+
const agentsDir = path7.join(this.cwd, installRoot, "opencode/agents");
|
|
1270
|
+
const skillsDir = path7.join(this.cwd, installRoot, "opencode/skills");
|
|
1271
|
+
await fs5.mkdir(agentsDir, { recursive: true });
|
|
1272
|
+
await fs5.mkdir(skillsDir, { recursive: true });
|
|
966
1273
|
for (const item of transformedItems) {
|
|
967
1274
|
const isAgent = PERSONA_MAPPING[item.name.toLowerCase()];
|
|
968
|
-
const targetDir = isAgent ? agentsDir :
|
|
1275
|
+
const targetDir = isAgent ? agentsDir : path7.join(skillsDir, item.name);
|
|
969
1276
|
const fileName = isAgent ? `${item.name}.md` : "SKILL.md";
|
|
970
1277
|
const absoluteTargetDir = isAgent ? agentsDir : targetDir;
|
|
971
|
-
await
|
|
972
|
-
await
|
|
1278
|
+
await fs5.mkdir(absoluteTargetDir, { recursive: true });
|
|
1279
|
+
await fs5.writeFile(path7.join(absoluteTargetDir, fileName), item.content);
|
|
973
1280
|
}
|
|
974
1281
|
await this.deployNativeExtensions(installRoot);
|
|
975
1282
|
await this.deployInstructions();
|
|
@@ -979,13 +1286,13 @@ Every task completion MUST provide the following payload:
|
|
|
979
1286
|
* Using hard copies instead of symlinks ensures native discovery across all platforms.
|
|
980
1287
|
*/
|
|
981
1288
|
async deployNativeExtensions(installRoot = ".ai-workflow") {
|
|
982
|
-
const antigravityDir =
|
|
983
|
-
const antigravityAgents =
|
|
984
|
-
const antigravitySkills =
|
|
985
|
-
const antigravityCommands =
|
|
986
|
-
await
|
|
987
|
-
await
|
|
988
|
-
await
|
|
1289
|
+
const antigravityDir = path7.join(this.cwd, ".agents");
|
|
1290
|
+
const antigravityAgents = path7.join(antigravityDir, "agents");
|
|
1291
|
+
const antigravitySkills = path7.join(antigravityDir, "skills");
|
|
1292
|
+
const antigravityCommands = path7.join(antigravityDir, "commands");
|
|
1293
|
+
await fs5.mkdir(antigravityAgents, { recursive: true });
|
|
1294
|
+
await fs5.mkdir(antigravitySkills, { recursive: true });
|
|
1295
|
+
await fs5.mkdir(antigravityCommands, { recursive: true });
|
|
989
1296
|
const getUrls = (targetPath) => {
|
|
990
1297
|
const linuxUrl = pathToFileURL(targetPath).href;
|
|
991
1298
|
const urls = [linuxUrl];
|
|
@@ -1027,8 +1334,8 @@ Every task completion MUST provide the following payload:
|
|
|
1027
1334
|
"user_rules"
|
|
1028
1335
|
]
|
|
1029
1336
|
};
|
|
1030
|
-
const sourceAgentsDir =
|
|
1031
|
-
const agentFiles = await
|
|
1337
|
+
const sourceAgentsDir = path7.join(this.cwd, installRoot, "opencode/agents");
|
|
1338
|
+
const agentFiles = await fs5.readdir(sourceAgentsDir).catch(() => []);
|
|
1032
1339
|
const AGENTS_INFO = {
|
|
1033
1340
|
"atlas": { name: "Atlas", description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
1034
1341
|
"nexus": { name: "Nexus", description: "Discovery, requirement, scope, and specification owner" },
|
|
@@ -1039,14 +1346,14 @@ Every task completion MUST provide the following payload:
|
|
|
1039
1346
|
};
|
|
1040
1347
|
for (const file of agentFiles) {
|
|
1041
1348
|
if (!file.endsWith(".md")) continue;
|
|
1042
|
-
const lowercaseName =
|
|
1349
|
+
const lowercaseName = path7.basename(file, ".md").toLowerCase();
|
|
1043
1350
|
const info = AGENTS_INFO[lowercaseName];
|
|
1044
1351
|
if (!info) continue;
|
|
1045
|
-
const agentFolder =
|
|
1046
|
-
await
|
|
1047
|
-
const content = await
|
|
1048
|
-
const mdTarget =
|
|
1049
|
-
await
|
|
1352
|
+
const agentFolder = path7.join(antigravityAgents, lowercaseName);
|
|
1353
|
+
await fs5.mkdir(agentFolder, { recursive: true });
|
|
1354
|
+
const content = await fs5.readFile(path7.join(sourceAgentsDir, file), "utf8");
|
|
1355
|
+
const mdTarget = path7.join(agentFolder, `${lowercaseName}.md`);
|
|
1356
|
+
await fs5.writeFile(mdTarget, content);
|
|
1050
1357
|
const agentJson = {
|
|
1051
1358
|
name: lowercaseName,
|
|
1052
1359
|
description: info.description,
|
|
@@ -1067,7 +1374,7 @@ ${getUrls(mdTarget)}`
|
|
|
1067
1374
|
}
|
|
1068
1375
|
}
|
|
1069
1376
|
};
|
|
1070
|
-
await
|
|
1377
|
+
await fs5.writeFile(path7.join(agentFolder, "agent.json"), JSON.stringify(agentJson, null, 2));
|
|
1071
1378
|
}
|
|
1072
1379
|
const SUBAGENTS_INFO = {
|
|
1073
1380
|
"Architecture-Specialist": { description: "Select the simplest architecture that safely satisfies current requirements", skill: "architecture" },
|
|
@@ -1092,9 +1399,9 @@ ${getUrls(mdTarget)}`
|
|
|
1092
1399
|
};
|
|
1093
1400
|
for (const [name, subInfo] of Object.entries(SUBAGENTS_INFO)) {
|
|
1094
1401
|
const lowercaseSubagentName = name.toLowerCase();
|
|
1095
|
-
const subagentFolder =
|
|
1096
|
-
await
|
|
1097
|
-
const skillPath =
|
|
1402
|
+
const subagentFolder = path7.join(antigravityAgents, lowercaseSubagentName);
|
|
1403
|
+
await fs5.mkdir(subagentFolder, { recursive: true });
|
|
1404
|
+
const skillPath = path7.join(antigravitySkills, subInfo.skill, "SKILL.md");
|
|
1098
1405
|
const subagentJson = {
|
|
1099
1406
|
name: lowercaseSubagentName,
|
|
1100
1407
|
description: subInfo.description,
|
|
@@ -1115,50 +1422,50 @@ ${getUrls(skillPath)}`
|
|
|
1115
1422
|
}
|
|
1116
1423
|
}
|
|
1117
1424
|
};
|
|
1118
|
-
await
|
|
1425
|
+
await fs5.writeFile(path7.join(subagentFolder, "agent.json"), JSON.stringify(subagentJson, null, 2));
|
|
1119
1426
|
}
|
|
1120
|
-
const sourceSkillsDir =
|
|
1121
|
-
const skillFolders = await
|
|
1427
|
+
const sourceSkillsDir = path7.join(this.cwd, installRoot, "opencode/skills");
|
|
1428
|
+
const skillFolders = await fs5.readdir(sourceSkillsDir).catch(() => []);
|
|
1122
1429
|
for (const folder of skillFolders) {
|
|
1123
|
-
const skillSource =
|
|
1430
|
+
const skillSource = path7.join(sourceSkillsDir, folder, "SKILL.md");
|
|
1124
1431
|
if (await this.exists(skillSource)) {
|
|
1125
|
-
await
|
|
1126
|
-
const content = await
|
|
1127
|
-
await
|
|
1432
|
+
await fs5.mkdir(path7.join(antigravitySkills, folder), { recursive: true });
|
|
1433
|
+
const content = await fs5.readFile(skillSource, "utf8");
|
|
1434
|
+
await fs5.writeFile(path7.join(antigravitySkills, folder, "SKILL.md"), content);
|
|
1128
1435
|
}
|
|
1129
1436
|
}
|
|
1130
|
-
const sourceCommandsDir =
|
|
1437
|
+
const sourceCommandsDir = path7.join(this.cwd, installRoot, "opencode/commands");
|
|
1131
1438
|
if (await this.exists(sourceCommandsDir)) {
|
|
1132
|
-
const commandFiles = await
|
|
1439
|
+
const commandFiles = await fs5.readdir(sourceCommandsDir).catch(() => []);
|
|
1133
1440
|
for (const file of commandFiles) {
|
|
1134
1441
|
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
1135
|
-
const content = await
|
|
1136
|
-
await
|
|
1442
|
+
const content = await fs5.readFile(path7.join(sourceCommandsDir, file), "utf8");
|
|
1443
|
+
await fs5.writeFile(path7.join(antigravityCommands, file), content);
|
|
1137
1444
|
}
|
|
1138
1445
|
}
|
|
1139
|
-
const sourcePoliciesDir =
|
|
1140
|
-
const antigravityPolicies =
|
|
1446
|
+
const sourcePoliciesDir = path7.join(this.cwd, installRoot, "opencode", "docs", "policies");
|
|
1447
|
+
const antigravityPolicies = path7.join(antigravityDir, "docs", "policies");
|
|
1141
1448
|
if (await this.exists(sourcePoliciesDir)) {
|
|
1142
|
-
await
|
|
1143
|
-
const policyFiles = await
|
|
1449
|
+
await fs5.mkdir(antigravityPolicies, { recursive: true });
|
|
1450
|
+
const policyFiles = await fs5.readdir(sourcePoliciesDir).catch(() => []);
|
|
1144
1451
|
for (const file of policyFiles) {
|
|
1145
1452
|
if (!file.endsWith(".md")) continue;
|
|
1146
|
-
const content = await
|
|
1147
|
-
await
|
|
1453
|
+
const content = await fs5.readFile(path7.join(sourcePoliciesDir, file), "utf8");
|
|
1454
|
+
await fs5.writeFile(path7.join(antigravityPolicies, file), content);
|
|
1148
1455
|
}
|
|
1149
1456
|
}
|
|
1150
|
-
const settingsPath =
|
|
1457
|
+
const settingsPath = path7.join(antigravityDir, "settings.json");
|
|
1151
1458
|
if (!await this.exists(settingsPath)) {
|
|
1152
1459
|
const settings = {
|
|
1153
1460
|
model: { name: "auto" },
|
|
1154
1461
|
ui: { theme: "Ayu" }
|
|
1155
1462
|
};
|
|
1156
|
-
await
|
|
1463
|
+
await fs5.writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
1157
1464
|
}
|
|
1158
1465
|
}
|
|
1159
1466
|
async exists(p) {
|
|
1160
1467
|
try {
|
|
1161
|
-
await
|
|
1468
|
+
await fs5.access(p);
|
|
1162
1469
|
return true;
|
|
1163
1470
|
} catch {
|
|
1164
1471
|
return false;
|
|
@@ -1168,10 +1475,10 @@ ${getUrls(skillPath)}`
|
|
|
1168
1475
|
* Deploys the main ANTIGRAVITY.md and .antigravityignore instructions to the project root.
|
|
1169
1476
|
*/
|
|
1170
1477
|
async deployInstructions() {
|
|
1171
|
-
const targetPath =
|
|
1172
|
-
const ignorePath =
|
|
1478
|
+
const targetPath = path7.join(this.cwd, "ANTIGRAVITY.md");
|
|
1479
|
+
const ignorePath = path7.join(this.cwd, ".antigravityignore");
|
|
1173
1480
|
try {
|
|
1174
|
-
await
|
|
1481
|
+
await fs5.access(targetPath);
|
|
1175
1482
|
} catch {
|
|
1176
1483
|
const instructions = `# AI Workflow Kit - Engineering Governance
|
|
1177
1484
|
|
|
@@ -1200,10 +1507,10 @@ All architectural changes must align with the specifications issued by the **Spe
|
|
|
1200
1507
|
- **Native Discovery**: This project uses workspace-specific extensions in \`.agents/\`.
|
|
1201
1508
|
- **Workflow Entry**: Use \`/atlas\` to begin any task.
|
|
1202
1509
|
`;
|
|
1203
|
-
await
|
|
1510
|
+
await fs5.writeFile(targetPath, instructions);
|
|
1204
1511
|
}
|
|
1205
1512
|
try {
|
|
1206
|
-
await
|
|
1513
|
+
await fs5.access(ignorePath);
|
|
1207
1514
|
} catch {
|
|
1208
1515
|
const ignoreContent = `.ai-workflow/
|
|
1209
1516
|
.ai-workflow-backups/
|
|
@@ -1216,7 +1523,7 @@ EVIDENCE.json
|
|
|
1216
1523
|
.agents/tmp/
|
|
1217
1524
|
.agents/history/
|
|
1218
1525
|
`;
|
|
1219
|
-
await
|
|
1526
|
+
await fs5.writeFile(ignorePath, ignoreContent);
|
|
1220
1527
|
}
|
|
1221
1528
|
}
|
|
1222
1529
|
};
|
|
@@ -1250,7 +1557,7 @@ function buildGitignoreBlock(entries) {
|
|
|
1250
1557
|
].join("\n");
|
|
1251
1558
|
}
|
|
1252
1559
|
async function upsertGitignoreBlock(cwd, entries) {
|
|
1253
|
-
const gitignorePath =
|
|
1560
|
+
const gitignorePath = path8.join(cwd, ".gitignore");
|
|
1254
1561
|
const block = buildGitignoreBlock(entries);
|
|
1255
1562
|
const beginMarker = "# BEGIN AI WORKFLOW KIT";
|
|
1256
1563
|
const endMarker = "# END AI WORKFLOW KIT";
|
|
@@ -1260,7 +1567,7 @@ async function upsertGitignoreBlock(cwd, entries) {
|
|
|
1260
1567
|
`);
|
|
1261
1568
|
return "created";
|
|
1262
1569
|
}
|
|
1263
|
-
const current = await
|
|
1570
|
+
const current = await fs6.readFile(gitignorePath, "utf8");
|
|
1264
1571
|
const beginIndex = current.indexOf(beginMarker);
|
|
1265
1572
|
const endIndex = current.indexOf(endMarker);
|
|
1266
1573
|
if (beginIndex >= 0 && endIndex > beginIndex) {
|
|
@@ -1281,7 +1588,7 @@ async function upsertGitignoreBlock(cwd, entries) {
|
|
|
1281
1588
|
return "updated";
|
|
1282
1589
|
}
|
|
1283
1590
|
async function injectScripts(cwd) {
|
|
1284
|
-
const pkgPath =
|
|
1591
|
+
const pkgPath = path8.join(cwd, "package.json");
|
|
1285
1592
|
if (!await exists(pkgPath)) return false;
|
|
1286
1593
|
try {
|
|
1287
1594
|
const pkg = await readJson(pkgPath);
|
|
@@ -1299,7 +1606,7 @@ async function injectScripts(cwd) {
|
|
|
1299
1606
|
async function verifySelfExecutionGuard(cwd, devMode) {
|
|
1300
1607
|
if (!devMode) {
|
|
1301
1608
|
try {
|
|
1302
|
-
const pkgPath =
|
|
1609
|
+
const pkgPath = path8.join(cwd, "package.json");
|
|
1303
1610
|
if (await exists(pkgPath)) {
|
|
1304
1611
|
const pkg = await readJson(pkgPath);
|
|
1305
1612
|
if (pkg.name === "@williambeto/ai-workflow") {
|
|
@@ -1347,8 +1654,8 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1347
1654
|
const linkBackups = [];
|
|
1348
1655
|
const linkCreated = [];
|
|
1349
1656
|
for (const { linkPath, targetPath } of linkEntries) {
|
|
1350
|
-
const absoluteLinkPath =
|
|
1351
|
-
const absoluteTargetPath =
|
|
1657
|
+
const absoluteLinkPath = path8.join(cwd, linkPath);
|
|
1658
|
+
const absoluteTargetPath = path8.join(cwd, targetPath);
|
|
1352
1659
|
if (!await exists(absoluteTargetPath)) {
|
|
1353
1660
|
throw new Error(`missing link target: ${targetPath}`);
|
|
1354
1661
|
}
|
|
@@ -1367,14 +1674,14 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1367
1674
|
maxPerFile: 20
|
|
1368
1675
|
});
|
|
1369
1676
|
linkBackups.push(backupPath);
|
|
1370
|
-
await
|
|
1677
|
+
await fs6.rm(absoluteLinkPath, { recursive: true, force: true });
|
|
1371
1678
|
}
|
|
1372
|
-
const stat = await
|
|
1679
|
+
const stat = await fs6.lstat(absoluteTargetPath);
|
|
1373
1680
|
const type = process.platform === "win32" ? stat.isDirectory() ? "junction" : "file" : stat.isDirectory() ? "dir" : "file";
|
|
1374
|
-
const linkTarget = process.platform === "win32" ? absoluteTargetPath :
|
|
1681
|
+
const linkTarget = process.platform === "win32" ? absoluteTargetPath : path8.relative(path8.dirname(absoluteLinkPath), absoluteTargetPath);
|
|
1375
1682
|
try {
|
|
1376
|
-
await
|
|
1377
|
-
await
|
|
1683
|
+
await fs6.mkdir(path8.dirname(absoluteLinkPath), { recursive: true });
|
|
1684
|
+
await fs6.symlink(linkTarget, absoluteLinkPath, type);
|
|
1378
1685
|
} catch (error) {
|
|
1379
1686
|
throw new Error(
|
|
1380
1687
|
`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.`
|
|
@@ -1387,9 +1694,9 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
|
|
|
1387
1694
|
async function executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity, force) {
|
|
1388
1695
|
if (platforms.length === 0) return;
|
|
1389
1696
|
console.log("ai-workflow: migrating to native platforms...");
|
|
1390
|
-
const agentsDir =
|
|
1391
|
-
const skillsDir =
|
|
1392
|
-
const agentFiles = (await
|
|
1697
|
+
const agentsDir = path8.join(cwd, installRoot, "opencode/agents");
|
|
1698
|
+
const skillsDir = path8.join(cwd, installRoot, "opencode/skills");
|
|
1699
|
+
const agentFiles = (await fs6.readdir(agentsDir).catch(() => [])).filter((f) => f.endsWith(".md")).map((f) => path8.join(agentsDir, f));
|
|
1393
1700
|
if (claude) {
|
|
1394
1701
|
const adapter = new ClaudeAdapter({ cwd });
|
|
1395
1702
|
await adapter.deploy(installRoot);
|
|
@@ -1402,10 +1709,10 @@ async function executePlatformMigrations(cwd, installRoot, platforms, claude, co
|
|
|
1402
1709
|
}
|
|
1403
1710
|
if (antigravity) {
|
|
1404
1711
|
const adapter = new AntigravityAdapter({ cwd });
|
|
1405
|
-
const skillFolders = await
|
|
1712
|
+
const skillFolders = await fs6.readdir(skillsDir).catch(() => []);
|
|
1406
1713
|
const skillFiles = [];
|
|
1407
1714
|
for (const folder of skillFolders) {
|
|
1408
|
-
const skillPath =
|
|
1715
|
+
const skillPath = path8.join(skillsDir, folder, "SKILL.md");
|
|
1409
1716
|
if (await exists(skillPath)) {
|
|
1410
1717
|
skillFiles.push(skillPath);
|
|
1411
1718
|
}
|
|
@@ -1537,15 +1844,15 @@ async function runInit({
|
|
|
1537
1844
|
}
|
|
1538
1845
|
|
|
1539
1846
|
// src/cli/commands/doctor.ts
|
|
1540
|
-
import
|
|
1541
|
-
import
|
|
1847
|
+
import fs7 from "fs/promises";
|
|
1848
|
+
import path9 from "path";
|
|
1542
1849
|
var REQUIRED_FILES = [
|
|
1543
1850
|
".ai-workflow",
|
|
1544
1851
|
"opencode/README.md"
|
|
1545
1852
|
];
|
|
1546
1853
|
async function isSymlink(filePath) {
|
|
1547
1854
|
try {
|
|
1548
|
-
const stat = await
|
|
1855
|
+
const stat = await fs7.lstat(filePath);
|
|
1549
1856
|
return stat.isSymbolicLink();
|
|
1550
1857
|
} catch {
|
|
1551
1858
|
return false;
|
|
@@ -1571,7 +1878,7 @@ async function checkOpenCodeRuntime(cwd, state) {
|
|
|
1571
1878
|
}
|
|
1572
1879
|
async function checkRequiredFiles(cwd, state) {
|
|
1573
1880
|
for (const relativePath of REQUIRED_FILES) {
|
|
1574
|
-
const ok = await exists(
|
|
1881
|
+
const ok = await exists(path9.join(cwd, relativePath));
|
|
1575
1882
|
if (ok) {
|
|
1576
1883
|
console.log(`PASS ${relativePath}`);
|
|
1577
1884
|
} else {
|
|
@@ -1581,8 +1888,8 @@ async function checkRequiredFiles(cwd, state) {
|
|
|
1581
1888
|
}
|
|
1582
1889
|
}
|
|
1583
1890
|
async function checkAiWorkflowConfig(cwd, state) {
|
|
1584
|
-
const configPathV2 =
|
|
1585
|
-
const configPathLegacy =
|
|
1891
|
+
const configPathV2 = path9.join(cwd, ".ai-workflow/config.json");
|
|
1892
|
+
const configPathLegacy = path9.join(cwd, ".ai-workflow.json");
|
|
1586
1893
|
let configPath = null;
|
|
1587
1894
|
if (await exists(configPathV2)) {
|
|
1588
1895
|
configPath = configPathV2;
|
|
@@ -1592,7 +1899,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1592
1899
|
if (!configPath) return;
|
|
1593
1900
|
try {
|
|
1594
1901
|
const config = await readJson(configPath);
|
|
1595
|
-
const basename =
|
|
1902
|
+
const basename = path9.basename(configPath);
|
|
1596
1903
|
if (config.installMode === "project-local" || config.mode === "standalone") {
|
|
1597
1904
|
console.log("PASS install mode is project-local/standalone");
|
|
1598
1905
|
} else {
|
|
@@ -1619,7 +1926,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1619
1926
|
}
|
|
1620
1927
|
if (Array.isArray(config.managedFiles)) {
|
|
1621
1928
|
for (const relativePath of config.managedFiles) {
|
|
1622
|
-
const fileExists = await exists(
|
|
1929
|
+
const fileExists = await exists(path9.join(cwd, relativePath));
|
|
1623
1930
|
if (!fileExists) {
|
|
1624
1931
|
state.hasWarning = true;
|
|
1625
1932
|
console.log(`WARN managed file missing: ${relativePath}`);
|
|
@@ -1628,7 +1935,7 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1628
1935
|
}
|
|
1629
1936
|
if (Array.isArray(config.managedLinks)) {
|
|
1630
1937
|
for (const relativePath of config.managedLinks) {
|
|
1631
|
-
const absolutePath =
|
|
1938
|
+
const absolutePath = path9.join(cwd, relativePath);
|
|
1632
1939
|
if (!await exists(absolutePath)) {
|
|
1633
1940
|
state.hasWarning = true;
|
|
1634
1941
|
console.log(`WARN managed link missing: ${relativePath}`);
|
|
@@ -1642,11 +1949,11 @@ async function checkAiWorkflowConfig(cwd, state) {
|
|
|
1642
1949
|
}
|
|
1643
1950
|
} catch {
|
|
1644
1951
|
state.hasFailure = true;
|
|
1645
|
-
console.log(`FAIL ${
|
|
1952
|
+
console.log(`FAIL ${path9.basename(configPath)} is not valid JSON`);
|
|
1646
1953
|
}
|
|
1647
1954
|
}
|
|
1648
1955
|
async function checkOpencodeJson(cwd, state) {
|
|
1649
|
-
const opencodePath =
|
|
1956
|
+
const opencodePath = path9.join(cwd, "opencode.jsonc");
|
|
1650
1957
|
if (!await exists(opencodePath)) {
|
|
1651
1958
|
state.hasFailure = true;
|
|
1652
1959
|
console.log("FAIL opencode.jsonc missing");
|
|
@@ -1692,7 +1999,7 @@ async function checkOpencodeJson(cwd, state) {
|
|
|
1692
1999
|
}
|
|
1693
2000
|
}
|
|
1694
2001
|
async function checkPackageJson(cwd, state) {
|
|
1695
|
-
const packageJsonPath =
|
|
2002
|
+
const packageJsonPath = path9.join(cwd, "package.json");
|
|
1696
2003
|
if (await exists(packageJsonPath)) {
|
|
1697
2004
|
try {
|
|
1698
2005
|
const packageJson = await readJson(packageJsonPath);
|
|
@@ -1715,7 +2022,7 @@ async function checkCodexRuntime(cwd, state) {
|
|
|
1715
2022
|
{ name: ".codex policies detected", path: ".codex/docs/policies" }
|
|
1716
2023
|
];
|
|
1717
2024
|
for (const check of checks) {
|
|
1718
|
-
const ok = await exists(
|
|
2025
|
+
const ok = await exists(path9.join(cwd, check.path));
|
|
1719
2026
|
if (ok) {
|
|
1720
2027
|
console.log(`PASS ${check.name}`);
|
|
1721
2028
|
} else {
|
|
@@ -1723,7 +2030,7 @@ async function checkCodexRuntime(cwd, state) {
|
|
|
1723
2030
|
console.log(`FAIL ${check.name}`);
|
|
1724
2031
|
}
|
|
1725
2032
|
}
|
|
1726
|
-
const codexMdOk = await exists(
|
|
2033
|
+
const codexMdOk = await exists(path9.join(cwd, "CODEX.md"));
|
|
1727
2034
|
if (codexMdOk) {
|
|
1728
2035
|
console.log(`PASS root CODEX.md exists`);
|
|
1729
2036
|
}
|
|
@@ -1806,7 +2113,8 @@ async function runCollectEvidence({
|
|
|
1806
2113
|
userRequest = null,
|
|
1807
2114
|
explicitApprovals = [],
|
|
1808
2115
|
visualDist = null,
|
|
1809
|
-
port = null
|
|
2116
|
+
port = null,
|
|
2117
|
+
workflowEvidence = null
|
|
1810
2118
|
}) {
|
|
1811
2119
|
const policies = resolvePoliciesFromLegacyMode(mode, evidencePolicy);
|
|
1812
2120
|
const qualityGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
|
|
@@ -1823,7 +2131,8 @@ async function runCollectEvidence({
|
|
|
1823
2131
|
userRequest,
|
|
1824
2132
|
explicitApprovals,
|
|
1825
2133
|
visualDist,
|
|
1826
|
-
port: port || 8080
|
|
2134
|
+
port: port || 8080,
|
|
2135
|
+
workflowEvidence
|
|
1827
2136
|
});
|
|
1828
2137
|
const evidence = await collector.collect(tasks, { writeArtifact: persist });
|
|
1829
2138
|
const summary = buildDeliverySummary({ evidence });
|
|
@@ -1838,11 +2147,11 @@ async function runCollectEvidence({
|
|
|
1838
2147
|
|
|
1839
2148
|
// src/core/healing/cli-remediation-executor.ts
|
|
1840
2149
|
import { spawnSync, execSync as execSync2 } from "child_process";
|
|
1841
|
-
import
|
|
1842
|
-
import
|
|
2150
|
+
import fs8 from "fs/promises";
|
|
2151
|
+
import path10 from "path";
|
|
1843
2152
|
async function readScripts(cwd) {
|
|
1844
2153
|
try {
|
|
1845
|
-
const pkg = JSON.parse(await
|
|
2154
|
+
const pkg = JSON.parse(await fs8.readFile(path10.join(cwd, "package.json"), "utf8"));
|
|
1846
2155
|
return pkg.scripts || {};
|
|
1847
2156
|
} catch {
|
|
1848
2157
|
return {};
|
|
@@ -1932,8 +2241,170 @@ function createCliRemediationExecutor(cwd) {
|
|
|
1932
2241
|
}
|
|
1933
2242
|
|
|
1934
2243
|
// src/cli/commands/run.ts
|
|
1935
|
-
import
|
|
2244
|
+
import path14 from "path";
|
|
2245
|
+
import fs12 from "fs/promises";
|
|
2246
|
+
|
|
2247
|
+
// src/core/sdd/approval-receipt.ts
|
|
2248
|
+
var import_ajv = __toESM(require_ajv(), 1);
|
|
2249
|
+
var import_ajv_formats = __toESM(require_dist(), 1);
|
|
1936
2250
|
import fs10 from "fs/promises";
|
|
2251
|
+
import path12 from "path";
|
|
2252
|
+
|
|
2253
|
+
// src/core/sdd/workflow-artifacts.ts
|
|
2254
|
+
import { createHash } from "crypto";
|
|
2255
|
+
import fs9 from "fs/promises";
|
|
2256
|
+
import path11 from "path";
|
|
2257
|
+
function sha256(content) {
|
|
2258
|
+
return createHash("sha256").update(content).digest("hex");
|
|
2259
|
+
}
|
|
2260
|
+
async function hashFile(filePath) {
|
|
2261
|
+
return sha256(await fs9.readFile(filePath));
|
|
2262
|
+
}
|
|
2263
|
+
async function writeFileAtomic(filePath, content) {
|
|
2264
|
+
await fs9.mkdir(path11.dirname(filePath), { recursive: true });
|
|
2265
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
2266
|
+
try {
|
|
2267
|
+
await fs9.writeFile(temporaryPath, content, { encoding: "utf8", flag: "wx" });
|
|
2268
|
+
await fs9.rename(temporaryPath, filePath);
|
|
2269
|
+
} catch (error) {
|
|
2270
|
+
await fs9.rm(temporaryPath, { force: true }).catch(() => {
|
|
2271
|
+
});
|
|
2272
|
+
throw error;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
function stripMarkdownFence(content) {
|
|
2276
|
+
const trimmed = content.trim();
|
|
2277
|
+
const match = trimmed.match(/^```(?:markdown|md|json)?\s*\n([\s\S]*?)\n```$/i);
|
|
2278
|
+
return (match ? match[1] : trimmed).trim() + "\n";
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/core/sdd/approval-receipt.ts
|
|
2282
|
+
var ApprovalReceiptValidator = class {
|
|
2283
|
+
cwd;
|
|
2284
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
2285
|
+
this.cwd = path12.resolve(cwd);
|
|
2286
|
+
}
|
|
2287
|
+
async validate(receiptPath, specPath, expectedWorkflowId) {
|
|
2288
|
+
const absoluteReceiptPath = path12.resolve(this.cwd, receiptPath);
|
|
2289
|
+
if (absoluteReceiptPath !== this.cwd && !absoluteReceiptPath.startsWith(`${this.cwd}${path12.sep}`)) {
|
|
2290
|
+
return { valid: false, reason: "Approval receipt must be inside the repository." };
|
|
2291
|
+
}
|
|
2292
|
+
try {
|
|
2293
|
+
const stat = await fs10.lstat(absoluteReceiptPath);
|
|
2294
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
2295
|
+
return { valid: false, reason: "Approval receipt must be a regular, non-symlink file." };
|
|
2296
|
+
}
|
|
2297
|
+
} catch {
|
|
2298
|
+
return { valid: false, reason: `Approval receipt not found: ${receiptPath}` };
|
|
2299
|
+
}
|
|
2300
|
+
let receipt;
|
|
2301
|
+
try {
|
|
2302
|
+
receipt = JSON.parse(await fs10.readFile(absoluteReceiptPath, "utf8"));
|
|
2303
|
+
const root = getPackageRoot();
|
|
2304
|
+
const schema = JSON.parse(await fs10.readFile(path12.join(root, "dist-assets/schemas/approval-receipt.schema.json"), "utf8"));
|
|
2305
|
+
const ajv = new import_ajv.default({ allErrors: true });
|
|
2306
|
+
(0, import_ajv_formats.default)(ajv);
|
|
2307
|
+
const validateSchema = ajv.compile(schema);
|
|
2308
|
+
if (!validateSchema(receipt)) {
|
|
2309
|
+
const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
|
|
2310
|
+
return { valid: false, reason: `Approval receipt schema violation: ${errors}` };
|
|
2311
|
+
}
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
return { valid: false, reason: `Approval receipt validation failed: ${error.message}` };
|
|
2314
|
+
}
|
|
2315
|
+
if (expectedWorkflowId && receipt.workflowId !== expectedWorkflowId) {
|
|
2316
|
+
return { valid: false, reason: `Approval receipt workflowId '${receipt.workflowId}' does not match '${expectedWorkflowId}'.` };
|
|
2317
|
+
}
|
|
2318
|
+
const approvedSpecHash = await hashFile(specPath);
|
|
2319
|
+
if (receipt.approvedSpecHash !== approvedSpecHash) {
|
|
2320
|
+
return { valid: false, reason: "Approval receipt hash does not match the approved specification bytes." };
|
|
2321
|
+
}
|
|
2322
|
+
return { valid: true, receipt, approvedSpecHash };
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
|
|
2326
|
+
// src/core/sdd/technical-plan-validator.ts
|
|
2327
|
+
var import_ajv2 = __toESM(require_ajv(), 1);
|
|
2328
|
+
import fs11 from "fs/promises";
|
|
2329
|
+
import path13 from "path";
|
|
2330
|
+
function isContained(root, candidate) {
|
|
2331
|
+
return candidate === root || candidate.startsWith(`${root}${path13.sep}`);
|
|
2332
|
+
}
|
|
2333
|
+
var TechnicalPlanValidator = class {
|
|
2334
|
+
cwd;
|
|
2335
|
+
constructor({ cwd = process.cwd() } = {}) {
|
|
2336
|
+
this.cwd = path13.resolve(cwd);
|
|
2337
|
+
}
|
|
2338
|
+
async validateScopedPath(candidate, forbiddenPaths) {
|
|
2339
|
+
if (path13.isAbsolute(candidate)) return `Absolute path is not allowed: '${candidate}'.`;
|
|
2340
|
+
const normalized = candidate.replaceAll("\\", "/");
|
|
2341
|
+
if (!normalized || normalized.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
2342
|
+
return `Path must be normalized without empty, '.' or '..' segments: '${candidate}'.`;
|
|
2343
|
+
}
|
|
2344
|
+
if (normalized === ".git" || normalized.startsWith(".git/") || normalized === ".ai-workflow" || normalized.startsWith(".ai-workflow/")) {
|
|
2345
|
+
return `Workflow control path is out of implementation scope: '${candidate}'.`;
|
|
2346
|
+
}
|
|
2347
|
+
const conflictsWithProtectedPath = [...forbiddenPaths].some(
|
|
2348
|
+
(protectedPath) => normalized === protectedPath || normalized.startsWith(`${protectedPath}/`) || protectedPath.startsWith(`${normalized}/`)
|
|
2349
|
+
);
|
|
2350
|
+
if (normalized === "EVIDENCE.json" || conflictsWithProtectedPath) {
|
|
2351
|
+
return `Protected workflow artifact is out of implementation scope: '${candidate}'.`;
|
|
2352
|
+
}
|
|
2353
|
+
const absolute = path13.resolve(this.cwd, normalized);
|
|
2354
|
+
if (!isContained(this.cwd, absolute)) return `Path escapes repository: '${candidate}'.`;
|
|
2355
|
+
let cursor = this.cwd;
|
|
2356
|
+
for (const part of normalized.split("/")) {
|
|
2357
|
+
cursor = path13.join(cursor, part);
|
|
2358
|
+
try {
|
|
2359
|
+
const stat = await fs11.lstat(cursor);
|
|
2360
|
+
if (cursor === absolute && stat.isDirectory()) {
|
|
2361
|
+
return `filesInScope must name explicit files, not a directory: '${candidate}'.`;
|
|
2362
|
+
}
|
|
2363
|
+
if (stat.isSymbolicLink()) {
|
|
2364
|
+
const resolved = await fs11.realpath(cursor);
|
|
2365
|
+
if (!isContained(this.cwd, resolved)) return `Symlink escapes repository: '${candidate}'.`;
|
|
2366
|
+
cursor = resolved;
|
|
2367
|
+
}
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
if (error.code !== "ENOENT") return `Cannot inspect scoped path '${candidate}': ${error.message}`;
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
async validateOutput(output, protectedPaths = []) {
|
|
2376
|
+
let plan;
|
|
2377
|
+
try {
|
|
2378
|
+
plan = JSON.parse(stripMarkdownFence(output));
|
|
2379
|
+
} catch (error) {
|
|
2380
|
+
return { valid: false, reason: `Technical plan is not valid JSON: ${error.message}` };
|
|
2381
|
+
}
|
|
2382
|
+
try {
|
|
2383
|
+
const schema = JSON.parse(await fs11.readFile(path13.join(getPackageRoot(), "dist-assets/schemas/technical-plan.schema.json"), "utf8"));
|
|
2384
|
+
const validateSchema = new import_ajv2.default({ allErrors: true }).compile(schema);
|
|
2385
|
+
if (!validateSchema(plan)) {
|
|
2386
|
+
const errors = (validateSchema.errors || []).map((error) => `${error.instancePath} ${error.message}`).join(", ");
|
|
2387
|
+
return { valid: false, reason: `Technical plan schema violation: ${errors}` };
|
|
2388
|
+
}
|
|
2389
|
+
} catch (error) {
|
|
2390
|
+
return { valid: false, reason: `Technical plan schema validation failed: ${error.message}` };
|
|
2391
|
+
}
|
|
2392
|
+
if (plan.filesInScope.length === 0) {
|
|
2393
|
+
return { valid: false, reason: "Technical plan filesInScope must not be empty." };
|
|
2394
|
+
}
|
|
2395
|
+
const forbidden = new Set(protectedPaths.map((entry) => path13.relative(this.cwd, path13.resolve(this.cwd, entry)).replaceAll("\\", "/")));
|
|
2396
|
+
for (const entry of plan.filesInScope) {
|
|
2397
|
+
const reason = await this.validateScopedPath(entry.path, forbidden);
|
|
2398
|
+
if (reason) return { valid: false, reason };
|
|
2399
|
+
}
|
|
2400
|
+
const content = `${JSON.stringify(plan, null, 2)}
|
|
2401
|
+
`;
|
|
2402
|
+
return { valid: true, plan, content };
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2405
|
+
|
|
2406
|
+
// src/cli/commands/run.ts
|
|
2407
|
+
import { execFileSync } from "child_process";
|
|
1937
2408
|
function normalizeTaskSlugFromSpec(specPath) {
|
|
1938
2409
|
const normalized = String(specPath).replaceAll("\\", "/");
|
|
1939
2410
|
const marker = "/docs/workflows/";
|
|
@@ -1942,7 +2413,7 @@ function normalizeTaskSlugFromSpec(specPath) {
|
|
|
1942
2413
|
const slug = normalized.slice(index).split("/")[0];
|
|
1943
2414
|
if (slug) return slug;
|
|
1944
2415
|
}
|
|
1945
|
-
return
|
|
2416
|
+
return path14.basename(specPath, path14.extname(specPath));
|
|
1946
2417
|
}
|
|
1947
2418
|
function combineValidation(evidence, quality) {
|
|
1948
2419
|
const statuses = [evidence.internalStatus, quality.overallStatus];
|
|
@@ -1954,27 +2425,194 @@ function combineValidation(evidence, quality) {
|
|
|
1954
2425
|
else if (statuses.includes("PASS_WITH_NOTES")) overallStatus = "PASS_WITH_NOTES";
|
|
1955
2426
|
return { overallStatus, evidence, quality };
|
|
1956
2427
|
}
|
|
1957
|
-
|
|
2428
|
+
function repositoryRelative(cwd, candidate) {
|
|
2429
|
+
return path14.relative(cwd, path14.resolve(cwd, candidate)).replaceAll("\\", "/");
|
|
2430
|
+
}
|
|
2431
|
+
async function findCompatibleLedger(cwd, specRelativePath) {
|
|
2432
|
+
const historyDir = path14.join(cwd, ".ai-workflow/history");
|
|
2433
|
+
const matches = [];
|
|
2434
|
+
for (const file of await fs12.readdir(historyDir).catch(() => [])) {
|
|
2435
|
+
if (!file.endsWith("-ledger.json")) continue;
|
|
2436
|
+
try {
|
|
2437
|
+
const events = JSON.parse(await fs12.readFile(path14.join(historyDir, file), "utf8"));
|
|
2438
|
+
if (events.some((event) => event.eventType === "specification_complete" && event.data?.artifactPath === specRelativePath)) {
|
|
2439
|
+
matches.push(path14.join(historyDir, file));
|
|
2440
|
+
}
|
|
2441
|
+
} catch {
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
return matches;
|
|
2445
|
+
}
|
|
2446
|
+
async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) {
|
|
2447
|
+
if (!approvalPath) throw new Error("[SDD BLOCKED] DEEP resume requires --approval-path.");
|
|
2448
|
+
const approvalValidator = new ApprovalReceiptValidator({ cwd });
|
|
2449
|
+
const approval = await approvalValidator.validate(approvalPath, absoluteSpecPath);
|
|
2450
|
+
if (!approval.valid || !approval.receipt || !approval.approvedSpecHash) {
|
|
2451
|
+
throw new Error(`[SDD BLOCKED] ${approval.reason || "Approval receipt is invalid."}`);
|
|
2452
|
+
}
|
|
2453
|
+
const workflowId = approval.receipt.workflowId;
|
|
2454
|
+
const ledgerPath = `.ai-workflow/history/${workflowId}-ledger.json`;
|
|
2455
|
+
const absoluteLedgerPath = path14.join(cwd, ledgerPath);
|
|
2456
|
+
const relativeSpecPath = repositoryRelative(cwd, specPath);
|
|
2457
|
+
const compatibleLedgers = await findCompatibleLedger(cwd, relativeSpecPath);
|
|
2458
|
+
if (compatibleLedgers.length > 1) {
|
|
2459
|
+
throw new Error("[SDD BLOCKED] Multiple ledgers claim the same approved specification.");
|
|
2460
|
+
}
|
|
2461
|
+
if (compatibleLedgers.length === 1 && path14.resolve(compatibleLedgers[0]) !== path14.resolve(absoluteLedgerPath)) {
|
|
2462
|
+
throw new Error("[SDD BLOCKED] Approval workflowId conflicts with the ledger that created the specification.");
|
|
2463
|
+
}
|
|
2464
|
+
const ledger = new EvidenceLedger({ cwd, workflowId });
|
|
2465
|
+
const loaded = await ledger.load(ledgerPath);
|
|
2466
|
+
if (!loaded) {
|
|
2467
|
+
ledger.logEvent({
|
|
2468
|
+
actor: "Atlas",
|
|
2469
|
+
actorType: "control-plane",
|
|
2470
|
+
observed: true,
|
|
2471
|
+
eventType: "workflow_attached",
|
|
2472
|
+
provenance: "sdd-control-plane",
|
|
2473
|
+
data: { specSource: "external", specPath: relativeSpecPath, status: "COMPLETED" }
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
const events = ledger.getEvents();
|
|
2477
|
+
const approvalEvents = events.filter((event) => event.eventType === "spec_approval_verified");
|
|
2478
|
+
if (approvalEvents.length > 1) throw new Error("[SDD BLOCKED] Duplicate specification approval events in ledger.");
|
|
2479
|
+
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.");
|
|
2480
|
+
const nexusTerminals = events.filter((event) => event.eventType === "specification_complete");
|
|
2481
|
+
if (nexusTerminals.length > 1) throw new Error("[SDD BLOCKED] Duplicate Nexus terminal events in ledger.");
|
|
2482
|
+
if (nexusTerminals.length === 1 && nexusTerminals[0].data?.artifactPath !== relativeSpecPath) {
|
|
2483
|
+
throw new Error("[SDD BLOCKED] Ledger specification path does not match --spec-path.");
|
|
2484
|
+
}
|
|
2485
|
+
const draftHash = nexusTerminals[0]?.data?.artifactHash || null;
|
|
2486
|
+
const controller = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
|
|
2487
|
+
const absolutePlanPath = path14.join(cwd, "docs/workflows", workflowId, "technical-plan.json");
|
|
2488
|
+
const relativePlanPath = repositoryRelative(cwd, absolutePlanPath);
|
|
2489
|
+
if (await fs12.access(absolutePlanPath).then(() => true).catch(() => false)) {
|
|
2490
|
+
throw new Error(`[SDD BLOCKED] Refusing to overwrite existing technical plan: ${relativePlanPath}`);
|
|
2491
|
+
}
|
|
2492
|
+
const planValidator = new TechnicalPlanValidator({ cwd });
|
|
2493
|
+
const protectedPaths = [absoluteSpecPath, approvalPath, ledgerPath, absolutePlanPath, "EVIDENCE.json"];
|
|
2494
|
+
const planning = await controller.executePhase({
|
|
2495
|
+
phase: "planning",
|
|
2496
|
+
actor: "Orion",
|
|
2497
|
+
prompt: [
|
|
2498
|
+
"Return only a JSON technical plan conforming to dist-assets/schemas/technical-plan.schema.json.",
|
|
2499
|
+
"Use explicit repository-relative file paths in filesInScope. Do not edit files or implement.",
|
|
2500
|
+
"Approved specification:",
|
|
2501
|
+
specContent
|
|
2502
|
+
].join("\n\n"),
|
|
2503
|
+
readOnly: true,
|
|
2504
|
+
requireExplicitActor: true,
|
|
2505
|
+
onOutput: async (output) => {
|
|
2506
|
+
const validation = await planValidator.validateOutput(output, protectedPaths);
|
|
2507
|
+
if (!validation.valid || !validation.content) throw new Error(validation.reason || "Technical plan is invalid.");
|
|
2508
|
+
await writeFileAtomic(absolutePlanPath, validation.content);
|
|
2509
|
+
return { artifactPath: relativePlanPath, artifactHash: sha256(validation.content) };
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
await ledger.save(ledgerPath);
|
|
2513
|
+
if (!planning.success || !planning.artifact?.artifactHash) {
|
|
2514
|
+
throw new Error(`[WORKFLOW BLOCKED] Orion planning phase failed: ${planning.error || "unknown failure"}`);
|
|
2515
|
+
}
|
|
2516
|
+
if (approvalEvents.length === 0) {
|
|
2517
|
+
ledger.logEvent({
|
|
2518
|
+
actor: "Atlas",
|
|
2519
|
+
actorType: "control-plane",
|
|
2520
|
+
observed: true,
|
|
2521
|
+
eventType: "spec_approval_verified",
|
|
2522
|
+
provenance: "approval-receipt",
|
|
2523
|
+
data: {
|
|
2524
|
+
approvalPath: repositoryRelative(cwd, approvalPath),
|
|
2525
|
+
approvedHash: approval.approvedSpecHash,
|
|
2526
|
+
draftHash,
|
|
2527
|
+
authorizationSource: approval.receipt.authorizationSource,
|
|
2528
|
+
status: "COMPLETED"
|
|
2529
|
+
}
|
|
2530
|
+
});
|
|
2531
|
+
await ledger.save(ledgerPath);
|
|
2532
|
+
}
|
|
2533
|
+
const planContent = await fs12.readFile(absolutePlanPath, "utf8");
|
|
2534
|
+
const parsedPlan = JSON.parse(planContent);
|
|
2535
|
+
const approvedHash = approval.approvedSpecHash;
|
|
2536
|
+
const planHash = String(planning.artifact.artifactHash);
|
|
2537
|
+
const baseSha = (() => {
|
|
2538
|
+
try {
|
|
2539
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", stdio: "pipe" }).trim();
|
|
2540
|
+
} catch {
|
|
2541
|
+
return "unknown";
|
|
2542
|
+
}
|
|
2543
|
+
})();
|
|
2544
|
+
const finalizer = new Finalizer({ cwd, fastTrack: false });
|
|
2545
|
+
const beforeImplementation = await finalizer.snapshotManager.capture();
|
|
2546
|
+
const allowedFiles = new Set(parsedPlan.filesInScope.map((entry) => String(entry.path).replaceAll("\\", "/")));
|
|
2547
|
+
const implementation = await controller.executePhase({
|
|
2548
|
+
phase: "implementation",
|
|
2549
|
+
actor: "Astra",
|
|
2550
|
+
prompt: [
|
|
2551
|
+
"Implement the approved specification using the validated technical plan.",
|
|
2552
|
+
`You may modify only these files: ${[...allowedFiles].join(", ")}`,
|
|
2553
|
+
"Do not modify the specification, approval receipt, technical plan, ledger, or evidence artifacts.",
|
|
2554
|
+
"Approved specification:",
|
|
2555
|
+
specContent,
|
|
2556
|
+
"Validated technical plan:",
|
|
2557
|
+
planContent
|
|
2558
|
+
].join("\n\n"),
|
|
2559
|
+
readOnly: false,
|
|
2560
|
+
requireExplicitActor: true,
|
|
2561
|
+
onOutput: async () => {
|
|
2562
|
+
if (await hashFile(absoluteSpecPath) !== approvedHash) throw new Error("Approved specification changed during Astra implementation.");
|
|
2563
|
+
if (await hashFile(absolutePlanPath) !== planHash) throw new Error("Technical plan changed during Astra implementation.");
|
|
2564
|
+
const integrity = await finalizer.verifyIntegrity(beforeImplementation);
|
|
2565
|
+
const workspaceStateMarkers = /* @__PURE__ */ new Set(["stagedDiff", "unstagedDiff", "untrackedFiles"]);
|
|
2566
|
+
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("\\", "/"));
|
|
2567
|
+
const outsideScope = changed.filter((entry) => !allowedFiles.has(entry));
|
|
2568
|
+
if (outsideScope.length > 0) throw new Error(`Astra modified files outside the technical plan: ${outsideScope.join(", ")}`);
|
|
2569
|
+
return { artifactPath: relativePlanPath, artifactHash: planHash };
|
|
2570
|
+
}
|
|
2571
|
+
});
|
|
2572
|
+
await ledger.save(ledgerPath);
|
|
2573
|
+
if (!implementation.success) {
|
|
2574
|
+
throw new Error(`[WORKFLOW BLOCKED] Astra implementation phase failed: ${implementation.error || "unknown failure"}`);
|
|
2575
|
+
}
|
|
2576
|
+
const phaseEvents = ledger.getEvents().filter(
|
|
2577
|
+
(event) => ["specification_complete", "planning_complete", "implementation_complete"].includes(event.eventType) && event.data?.success !== false
|
|
2578
|
+
);
|
|
2579
|
+
return {
|
|
2580
|
+
workflowId,
|
|
2581
|
+
baseSha,
|
|
2582
|
+
artifactHashes: { draft: draftHash, approved: approvedHash, technicalPlan: planHash },
|
|
2583
|
+
phaseOrder: phaseEvents.map((event) => event.data?.phase || event.eventType.replace("_complete", "")),
|
|
2584
|
+
confirmedActors: phaseEvents.filter((event) => event.data?.runtimeAgentApplied).map((event) => ({
|
|
2585
|
+
phase: event.data.phase || event.eventType.replace("_complete", ""),
|
|
2586
|
+
requested: event.data.runtimeAgentRequested,
|
|
2587
|
+
applied: event.data.runtimeAgentApplied
|
|
2588
|
+
})),
|
|
2589
|
+
ledger,
|
|
2590
|
+
ledgerPath,
|
|
2591
|
+
controller
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null }) {
|
|
1958
2595
|
console.log("\n[AI WORKFLOW] Starting evidence-backed orchestration...\n");
|
|
1959
|
-
const branchGate = new BranchGate({ memoryDir:
|
|
2596
|
+
const branchGate = new BranchGate({ memoryDir: path14.join(cwd, ".ai-workflow"), cwd });
|
|
1960
2597
|
const taskSlug = specPath ? normalizeTaskSlugFromSpec(specPath) : "implementation";
|
|
1961
2598
|
const gateResult = branchGate.check(override, { taskSlug });
|
|
1962
2599
|
if (gateResult.blocked) throw new Error(`[GATE BLOCKED] ${gateResult.reason}`);
|
|
1963
2600
|
console.log(`[PASS] Branch Gate: ${gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : `${gateResult.branch} is authorized`}.`);
|
|
1964
2601
|
if (!specPath) throw new Error("[SDD BLOCKED] Missing --spec-path. An approved specification is required.");
|
|
1965
2602
|
const validator = new SpecValidator();
|
|
1966
|
-
const absoluteSpecPath =
|
|
2603
|
+
const absoluteSpecPath = path14.isAbsolute(specPath) ? specPath : path14.join(cwd, specPath);
|
|
1967
2604
|
const validation = await validator.validate(absoluteSpecPath);
|
|
1968
2605
|
if (!validation.valid) throw new Error(`[SDD BLOCKED] Specification is not ready: ${validation.reason}`);
|
|
1969
2606
|
const specTier = validation.tier || "standard";
|
|
1970
2607
|
const riskLevel = specTier === "deep" ? "high" : specTier === "tiny" ? "low" : "medium";
|
|
1971
2608
|
const evidencePolicy = specTier === "deep" ? "required" : "optional";
|
|
1972
2609
|
const remediationLimit = specTier === "deep" ? 3 : specTier === "tiny" ? 1 : 2;
|
|
1973
|
-
const specContent = await
|
|
2610
|
+
const specContent = await fs12.readFile(absoluteSpecPath, "utf8");
|
|
1974
2611
|
const workflowProfile = resolveWorkflowProfile({ request: specContent });
|
|
1975
2612
|
const profileDefinition = getWorkflowProfile(workflowProfile);
|
|
1976
2613
|
console.log(`[PASS] Specification: ${specTier.toUpperCase()} spec is APPROVED.`);
|
|
1977
2614
|
console.log(`[PROFILE] ${workflowProfile} -> ${profileDefinition.owner}; skills: ${profileDefinition.skills.join(", ") || "none"}.`);
|
|
2615
|
+
const deepContext = specTier === "deep" ? await runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) : null;
|
|
1978
2616
|
const validateWorkflow = async () => {
|
|
1979
2617
|
const evidence = await runCollectEvidence({
|
|
1980
2618
|
cwd,
|
|
@@ -1982,18 +2620,36 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
1982
2620
|
taskSlug,
|
|
1983
2621
|
evidencePolicy,
|
|
1984
2622
|
profile: workflowProfile,
|
|
1985
|
-
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED"
|
|
2623
|
+
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
|
|
2624
|
+
workflowEvidence: deepContext ? {
|
|
2625
|
+
workflowId: deepContext.workflowId,
|
|
2626
|
+
baseSha: deepContext.baseSha,
|
|
2627
|
+
artifactHashes: deepContext.artifactHashes,
|
|
2628
|
+
phaseOrder: deepContext.phaseOrder,
|
|
2629
|
+
confirmedActors: deepContext.confirmedActors
|
|
2630
|
+
} : null
|
|
1986
2631
|
});
|
|
1987
2632
|
const quality = await new QualityGuard({ cwd, taskSlug, evidencePolicy, riskLevel }).verify();
|
|
1988
|
-
|
|
2633
|
+
const combined = combineValidation(evidence, quality);
|
|
2634
|
+
if (deepContext) {
|
|
2635
|
+
const workflowEvidence = await new EvidenceValidator({ cwd, requireWorkflowEvidence: true }).validate();
|
|
2636
|
+
if (!workflowEvidence.valid) {
|
|
2637
|
+
combined.overallStatus = "FAIL_QUALITY_GATE";
|
|
2638
|
+
combined.reason = workflowEvidence.reason;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
return combined;
|
|
1989
2642
|
};
|
|
1990
|
-
|
|
2643
|
+
const observedValidate = async () => deepContext ? await deepContext.controller.validate(taskSlug, workflowProfile, validateWorkflow, evidencePolicy) : await validateWorkflow();
|
|
2644
|
+
let result = await observedValidate();
|
|
2645
|
+
if (deepContext) await deepContext.ledger.save(deepContext.ledgerPath);
|
|
1991
2646
|
if (isRecoverableGateFailure(result.overallStatus)) {
|
|
1992
2647
|
console.log(`
|
|
1993
2648
|
[REMEDIATION REQUIRED] ${result.overallStatus}. Starting bounded remediation (limit: ${remediationLimit}).`);
|
|
1994
2649
|
const executor = typeof remediationExecutor === "function" ? remediationExecutor : createCliRemediationExecutor(cwd);
|
|
1995
2650
|
const healer = new HealerEngine({ cwd, remediationLimit, taskSlug });
|
|
1996
|
-
result = await healer.run({ initialResult: result, validate:
|
|
2651
|
+
result = await healer.run({ initialResult: result, validate: observedValidate, remediate: executor });
|
|
2652
|
+
if (deepContext) await deepContext.ledger.save(deepContext.ledgerPath);
|
|
1997
2653
|
}
|
|
1998
2654
|
if (isTerminalFailure(result.overallStatus)) {
|
|
1999
2655
|
throw new Error(`[WORKFLOW BLOCKED] ${result.overallStatus}: ${result.reason || "Validation could not be resolved safely."}`);
|
|
@@ -2004,7 +2660,7 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
2004
2660
|
console.log("\n--- Final Handoff ---");
|
|
2005
2661
|
const handoffEngine = new HandoffEngine({ cwd });
|
|
2006
2662
|
const handoffPath = await handoffEngine.generate({
|
|
2007
|
-
taskId:
|
|
2663
|
+
taskId: path14.basename(specPath, ".md"),
|
|
2008
2664
|
status: result.overallStatus,
|
|
2009
2665
|
specPaths: [specPath],
|
|
2010
2666
|
evidence: result.evidence,
|
|
@@ -2013,14 +2669,14 @@ async function runMasterOrchestrator({ cwd, specPath, override, remediationExecu
|
|
|
2013
2669
|
console.log(`
|
|
2014
2670
|
[AI WORKFLOW COMPLETE] ${result.overallStatus}`);
|
|
2015
2671
|
if (result.remediation?.attempts) console.log(`Remediation attempts: ${result.remediation.attempts}`);
|
|
2016
|
-
console.log(`Handoff Packet: ${
|
|
2672
|
+
console.log(`Handoff Packet: ${path14.relative(cwd, handoffPath)}
|
|
2017
2673
|
`);
|
|
2018
|
-
return { ...result, workflowProfile };
|
|
2674
|
+
return { ...result, workflowProfile, workflowId: deepContext?.workflowId || null };
|
|
2019
2675
|
}
|
|
2020
2676
|
|
|
2021
2677
|
// src/core/healing/runtime-remediation-executor.ts
|
|
2022
|
-
import
|
|
2023
|
-
import
|
|
2678
|
+
import fs13 from "fs/promises";
|
|
2679
|
+
import path15 from "path";
|
|
2024
2680
|
import { execSync as execSync3 } from "child_process";
|
|
2025
2681
|
function createRuntimeRemediationExecutor(cwd, ledger = null) {
|
|
2026
2682
|
const adapter = new OpenCodeAdapter({ cwd });
|
|
@@ -2060,14 +2716,14 @@ function createRuntimeRemediationExecutor(cwd, ledger = null) {
|
|
|
2060
2716
|
}
|
|
2061
2717
|
let requestContent = {};
|
|
2062
2718
|
try {
|
|
2063
|
-
requestContent = JSON.parse(await
|
|
2719
|
+
requestContent = JSON.parse(await fs13.readFile(requestPath, "utf8"));
|
|
2064
2720
|
} catch {
|
|
2065
2721
|
}
|
|
2066
2722
|
const filesBefore = getChangedFiles2();
|
|
2067
2723
|
const fileContentsBefore = {};
|
|
2068
2724
|
for (const file of filesBefore) {
|
|
2069
2725
|
try {
|
|
2070
|
-
fileContentsBefore[file] = await
|
|
2726
|
+
fileContentsBefore[file] = await fs13.readFile(path15.join(cwd, file), "utf8");
|
|
2071
2727
|
} catch {
|
|
2072
2728
|
fileContentsBefore[file] = null;
|
|
2073
2729
|
}
|
|
@@ -2105,7 +2761,7 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
|
|
|
2105
2761
|
changedFiles.push(file);
|
|
2106
2762
|
} else {
|
|
2107
2763
|
try {
|
|
2108
|
-
const contentAfter = await
|
|
2764
|
+
const contentAfter = await fs13.readFile(path15.join(cwd, file), "utf8");
|
|
2109
2765
|
if (contentAfter !== fileContentsBefore[file]) {
|
|
2110
2766
|
changedFiles.push(file);
|
|
2111
2767
|
}
|
|
@@ -2142,8 +2798,8 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
|
|
|
2142
2798
|
}
|
|
2143
2799
|
|
|
2144
2800
|
// src/cli/commands/execute.ts
|
|
2145
|
-
import
|
|
2146
|
-
import
|
|
2801
|
+
import fs14 from "fs/promises";
|
|
2802
|
+
import path16 from "path";
|
|
2147
2803
|
import { execSync as execSync4 } from "child_process";
|
|
2148
2804
|
async function captureReadOnlyState(cwd, fastTrack = false) {
|
|
2149
2805
|
const finalizer = new Finalizer({ cwd, fastTrack });
|
|
@@ -2249,12 +2905,12 @@ function runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate) {
|
|
|
2249
2905
|
}
|
|
2250
2906
|
async function createSpecIfRequired(plan, classification, taskSlug, cwd) {
|
|
2251
2907
|
if (plan.specPath) {
|
|
2252
|
-
const fullSpecPath =
|
|
2253
|
-
const specExists = await
|
|
2908
|
+
const fullSpecPath = path16.join(cwd, plan.specPath);
|
|
2909
|
+
const specExists = await fs14.access(fullSpecPath).then(() => true).catch(() => false);
|
|
2254
2910
|
if (!specExists) {
|
|
2255
|
-
await
|
|
2256
|
-
const specTemplatePath =
|
|
2257
|
-
const templateContent = await
|
|
2911
|
+
await fs14.mkdir(path16.dirname(fullSpecPath), { recursive: true });
|
|
2912
|
+
const specTemplatePath = path16.join(cwd, ".ai-workflow/templates/specs/standard.md");
|
|
2913
|
+
const templateContent = await fs14.readFile(specTemplatePath, "utf8").catch(() => {
|
|
2258
2914
|
return `# [STANDARD] Specification: ${classification.request}
|
|
2259
2915
|
|
|
2260
2916
|
## Metadata
|
|
@@ -2282,11 +2938,87 @@ async function createSpecIfRequired(plan, classification, taskSlug, cwd) {
|
|
|
2282
2938
|
|
|
2283
2939
|
- [ ] Behavior tests pass`;
|
|
2284
2940
|
});
|
|
2285
|
-
await
|
|
2941
|
+
await fs14.writeFile(fullSpecPath, templateContent);
|
|
2286
2942
|
console.log(`[EXECUTE] Created DRAFT specification template at: ${plan.specPath}`);
|
|
2287
2943
|
}
|
|
2288
2944
|
}
|
|
2289
2945
|
}
|
|
2946
|
+
async function runDeepSpecificationCheckpoint(plan, classification, taskSlug, cwd, delegationController, stateMachine) {
|
|
2947
|
+
if (!plan.specPath) throw new Error("[SDD BLOCKED] Required specification path was not planned.");
|
|
2948
|
+
const absoluteSpecPath = path16.resolve(cwd, plan.specPath);
|
|
2949
|
+
const relativeSpecPath = path16.relative(cwd, absoluteSpecPath).replaceAll("\\", "/");
|
|
2950
|
+
if (relativeSpecPath.startsWith("../") || path16.isAbsolute(relativeSpecPath)) {
|
|
2951
|
+
throw new Error("[SDD BLOCKED] Planned specification path escapes the repository.");
|
|
2952
|
+
}
|
|
2953
|
+
if (await fs14.access(absoluteSpecPath).then(() => true).catch(() => false)) {
|
|
2954
|
+
throw new Error(`[SDD BLOCKED] Refusing to overwrite existing specification: ${relativeSpecPath}`);
|
|
2955
|
+
}
|
|
2956
|
+
const approvalPath = `.ai-workflow/approvals/${taskSlug}.json`;
|
|
2957
|
+
const validator = new SpecValidator();
|
|
2958
|
+
stateMachine.transitionTo("DELEGATED");
|
|
2959
|
+
const prompt = [
|
|
2960
|
+
"Produce only the complete Markdown for a [DEEP] specification.",
|
|
2961
|
+
"The Metadata Status must be exactly DRAFT. Include concrete acceptance criteria as unchecked checkboxes.",
|
|
2962
|
+
"Do not approve the specification, create a receipt, edit files, or implement code.",
|
|
2963
|
+
`Workflow ID: ${taskSlug}`,
|
|
2964
|
+
`Request: ${classification.request}`
|
|
2965
|
+
].join("\n");
|
|
2966
|
+
const phaseResult = await delegationController.executePhase({
|
|
2967
|
+
phase: "specification",
|
|
2968
|
+
actor: "Nexus",
|
|
2969
|
+
prompt,
|
|
2970
|
+
readOnly: true,
|
|
2971
|
+
requireExplicitActor: true,
|
|
2972
|
+
onOutput: async (output) => {
|
|
2973
|
+
const content = stripMarkdownFence(output);
|
|
2974
|
+
const validation = validator.validateDraftContent(content);
|
|
2975
|
+
if (!validation.valid) throw new Error(`Nexus DRAFT rejected: ${validation.reason}`);
|
|
2976
|
+
await writeFileAtomic(absoluteSpecPath, content);
|
|
2977
|
+
return { artifactPath: relativeSpecPath, artifactHash: sha256(content), draftHash: sha256(content) };
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
if (!phaseResult.success) {
|
|
2981
|
+
stateMachine.transitionTo("BLOCKED");
|
|
2982
|
+
throw new Error(`[SDD BLOCKED] Nexus specification phase failed: ${phaseResult.error || "unknown failure"}`);
|
|
2983
|
+
}
|
|
2984
|
+
const draftHash = String(phaseResult.artifact?.artifactHash || "");
|
|
2985
|
+
delegationController.ledger?.logEvent({
|
|
2986
|
+
actor: "Atlas",
|
|
2987
|
+
actorType: "control-plane",
|
|
2988
|
+
observed: true,
|
|
2989
|
+
eventType: "approval_checkpoint",
|
|
2990
|
+
provenance: "sdd-control-plane",
|
|
2991
|
+
data: {
|
|
2992
|
+
specSource: "nexus",
|
|
2993
|
+
specPath: relativeSpecPath,
|
|
2994
|
+
approvalPath,
|
|
2995
|
+
draftHash,
|
|
2996
|
+
approvedHash: null,
|
|
2997
|
+
planHash: null,
|
|
2998
|
+
status: "BLOCKED",
|
|
2999
|
+
reason: "SDD APPROVAL REQUIRED"
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
stateMachine.transitionTo("BLOCKED");
|
|
3003
|
+
const resumeCommand = `aw run --spec-path=${relativeSpecPath} --approval-path=${approvalPath}`;
|
|
3004
|
+
console.log(`
|
|
3005
|
+
[BLOCKED] SDD APPROVAL REQUIRED`);
|
|
3006
|
+
console.log(`Specification: ${relativeSpecPath}`);
|
|
3007
|
+
console.log(`Resume: ${resumeCommand}
|
|
3008
|
+
`);
|
|
3009
|
+
return {
|
|
3010
|
+
overallStatus: "BLOCKED",
|
|
3011
|
+
reason: "SDD APPROVAL REQUIRED",
|
|
3012
|
+
checkpoint: "approval",
|
|
3013
|
+
exitCode: 2,
|
|
3014
|
+
workflowId: taskSlug,
|
|
3015
|
+
specPath: relativeSpecPath,
|
|
3016
|
+
approvalPath,
|
|
3017
|
+
draftHash,
|
|
3018
|
+
resumeCommand,
|
|
3019
|
+
stateHistory: stateMachine.getHistory()
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
2290
3022
|
async function runImplementation(plan, classification, delegationController, stateMachine, fastTrack = false) {
|
|
2291
3023
|
stateMachine.transitionTo("DELEGATED");
|
|
2292
3024
|
stateMachine.transitionTo("IMPLEMENTING");
|
|
@@ -2399,7 +3131,7 @@ async function generateFinalHandoff(plan, taskSlug, cwd, finalState, evidence) {
|
|
|
2399
3131
|
});
|
|
2400
3132
|
console.log(`
|
|
2401
3133
|
[AI WORKFLOW COMPLETE] ${finalState}`);
|
|
2402
|
-
console.log(`Handoff Packet: ${
|
|
3134
|
+
console.log(`Handoff Packet: ${path16.relative(cwd, handoffPath)}
|
|
2403
3135
|
`);
|
|
2404
3136
|
return handoffPath;
|
|
2405
3137
|
}
|
|
@@ -2409,12 +3141,12 @@ async function runFastTrackValidation(cwd, result, checkBranchSafety) {
|
|
|
2409
3141
|
checkBranchSafety();
|
|
2410
3142
|
const changedFiles = result.evidence?.changedFiles || [];
|
|
2411
3143
|
console.log(`[FAST-TRACK] Changed files detected:`, changedFiles);
|
|
2412
|
-
const evidencePath =
|
|
2413
|
-
const hasEvidence = await
|
|
3144
|
+
const evidencePath = path16.join(cwd, "EVIDENCE.json");
|
|
3145
|
+
const hasEvidence = await fs14.access(evidencePath).then(() => true).catch(() => false);
|
|
2414
3146
|
if (hasEvidence) {
|
|
2415
|
-
const { EvidenceValidator } = await import("../evidence-validator-
|
|
3147
|
+
const { EvidenceValidator: EvidenceValidator2 } = await import("../evidence-validator-HS3NTWAB.js");
|
|
2416
3148
|
const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
|
|
2417
|
-
const evidenceValidator = new
|
|
3149
|
+
const evidenceValidator = new EvidenceValidator2({ cwd, skipFileCheck: isTest, skipGitCheck: isTest });
|
|
2418
3150
|
const valResult = await evidenceValidator.validate();
|
|
2419
3151
|
if (!valResult.valid) {
|
|
2420
3152
|
console.error(`
|
|
@@ -2484,7 +3216,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
2484
3216
|
const { plan: planObj, classification } = await classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegationController, stateMachine);
|
|
2485
3217
|
plan = planObj;
|
|
2486
3218
|
const fastTrack = plan.riskLevel === "low" && (!plan.branchNeeded || plan.evidencePolicy === "optional");
|
|
2487
|
-
const branchGate = new BranchGate({ memoryDir:
|
|
3219
|
+
const branchGate = new BranchGate({ memoryDir: path16.join(cwd, ".ai-workflow"), cwd });
|
|
2488
3220
|
const gateResult = runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate);
|
|
2489
3221
|
const checkBranchSafety = () => {
|
|
2490
3222
|
if (!planObj.branchNeeded) return;
|
|
@@ -2494,6 +3226,9 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
2494
3226
|
throw new Error(`[WORKFLOW BLOCKED] Security violation: current branch is protected '${currentBranch}'!`);
|
|
2495
3227
|
}
|
|
2496
3228
|
};
|
|
3229
|
+
if (classification.specPolicy === "required") {
|
|
3230
|
+
return await runDeepSpecificationCheckpoint(plan, classification, taskSlug, cwd, delegationController, stateMachine);
|
|
3231
|
+
}
|
|
2497
3232
|
await createSpecIfRequired(plan, classification, taskSlug, cwd);
|
|
2498
3233
|
let readOnlyStateBefore = null;
|
|
2499
3234
|
if (!plan.branchNeeded) {
|
|
@@ -2569,6 +3304,7 @@ ${JSON.stringify(ledger.getEvents(), null, 2)}
|
|
|
2569
3304
|
try {
|
|
2570
3305
|
await ledger.save(`.ai-workflow/history/${taskSlug}-ledger.json`);
|
|
2571
3306
|
} catch (err) {
|
|
3307
|
+
if (plan?.riskLevel === "high") throw err;
|
|
2572
3308
|
}
|
|
2573
3309
|
}
|
|
2574
3310
|
}
|
|
@@ -2605,7 +3341,7 @@ async function runClean({ cwd, yes = false, dryRun = false, purgeAgents = false
|
|
|
2605
3341
|
"opencode"
|
|
2606
3342
|
];
|
|
2607
3343
|
const hasAgents = existsSync(join(cwd, ".agents"));
|
|
2608
|
-
const existingTargets = targets.map((name) => join(cwd, name)).filter((
|
|
3344
|
+
const existingTargets = targets.map((name) => join(cwd, name)).filter((path17) => existsSync(path17) || isSymlink2(path17));
|
|
2609
3345
|
if (existingTargets.length === 0 && (!hasAgents || !purgeAgents)) {
|
|
2610
3346
|
console.log("No AI Workflow configuration or files found in the current project.");
|
|
2611
3347
|
return;
|
|
@@ -2614,7 +3350,7 @@ async function runClean({ cwd, yes = false, dryRun = false, purgeAgents = false
|
|
|
2614
3350
|
console.log("===============================");
|
|
2615
3351
|
if (dryRun) {
|
|
2616
3352
|
console.log("[DRY RUN] The following items would be deleted:");
|
|
2617
|
-
existingTargets.forEach((
|
|
3353
|
+
existingTargets.forEach((path17) => console.log(` - ${path17}`));
|
|
2618
3354
|
if (hasAgents && purgeAgents) {
|
|
2619
3355
|
console.log(` - ${join(cwd, ".agents")} (customizations folder)`);
|
|
2620
3356
|
} else if (hasAgents) {
|
|
@@ -2668,12 +3404,14 @@ function printHelp() {
|
|
|
2668
3404
|
|
|
2669
3405
|
Usage:
|
|
2670
3406
|
ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"]
|
|
2671
|
-
ai-workflow run --spec-path=<path>
|
|
3407
|
+
ai-workflow run --spec-path=<path> [--approval-path=<path>]
|
|
2672
3408
|
ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--claude] [--codex] [--antigravity] [--profile=<profile>]
|
|
2673
3409
|
ai-workflow validate [--a11y] [--visual-dist=<path>] [--port=<port>]
|
|
2674
3410
|
ai-workflow collect-evidence [--task=<slug>] [--evidence-policy=<optional|required>] [--mode=<quick|standard|full>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
|
|
2675
3411
|
ai-workflow doctor
|
|
2676
3412
|
ai-workflow clean [--yes] [--dry-run] [--purge-agents]
|
|
3413
|
+
ai-workflow lint skills
|
|
3414
|
+
ai-workflow skill create <name> [--description="<desc>"] [--dry-run] [--force]
|
|
2677
3415
|
|
|
2678
3416
|
Commands:
|
|
2679
3417
|
execute Orchestrate execution of a natural request through the state machine
|
|
@@ -2682,10 +3420,13 @@ Commands:
|
|
|
2682
3420
|
collect-evidence Run observed project validation; persists EVIDENCE.json under required evidence policy
|
|
2683
3421
|
doctor Verify local ai-workflow installation
|
|
2684
3422
|
clean De-initialize and remove AI Workflow files and symlinks from the project
|
|
3423
|
+
lint Run specialized linters (e.g. lint skills)
|
|
3424
|
+
skill Manage AI workflow skills (e.g. create)
|
|
2685
3425
|
`);
|
|
2686
3426
|
}
|
|
2687
3427
|
function parseFlags(args) {
|
|
2688
3428
|
const specPathArg = args.find((arg) => arg.startsWith("--spec-path="));
|
|
3429
|
+
const approvalPathArg = args.find((arg) => arg.startsWith("--approval-path="));
|
|
2689
3430
|
const profileEqArg = args.find((arg) => arg.startsWith("--profile="));
|
|
2690
3431
|
const taskArg = args.find((arg) => arg.startsWith("--task="));
|
|
2691
3432
|
const modeArg = args.find((arg) => arg.startsWith("--mode="));
|
|
@@ -2706,6 +3447,7 @@ function parseFlags(args) {
|
|
|
2706
3447
|
antigravity: args.includes("--antigravity"),
|
|
2707
3448
|
"dev-mode": args.includes("--dev-mode"),
|
|
2708
3449
|
specPath: specPathArg ? specPathArg.replace("--spec-path=", "") : void 0,
|
|
3450
|
+
approvalPath: approvalPathArg ? approvalPathArg.replace("--approval-path=", "") : void 0,
|
|
2709
3451
|
profile: profileVal || void 0,
|
|
2710
3452
|
taskSlug: taskArg ? taskArg.replace("--task=", "") : void 0,
|
|
2711
3453
|
mode: modeArg ? modeArg.replace("--mode=", "") : void 0,
|
|
@@ -2727,7 +3469,7 @@ var commandMap = {
|
|
|
2727
3469
|
taskSlug: flags.taskSlug
|
|
2728
3470
|
});
|
|
2729
3471
|
if (result && (result.overallStatus === "FAIL_QUALITY_GATE" || result.overallStatus === "BLOCKED" || result.overallStatus === "FAIL")) {
|
|
2730
|
-
process.
|
|
3472
|
+
process.exitCode = result.exitCode === 2 ? 2 : 1;
|
|
2731
3473
|
}
|
|
2732
3474
|
},
|
|
2733
3475
|
run: async (_, flags) => {
|
|
@@ -2757,7 +3499,7 @@ var commandMap = {
|
|
|
2757
3499
|
});
|
|
2758
3500
|
},
|
|
2759
3501
|
validate: async (_, flags) => {
|
|
2760
|
-
const { runValidate } = await import("../validate-
|
|
3502
|
+
const { runValidate } = await import("../validate-IEHLAVZ6.js");
|
|
2761
3503
|
await runValidate({
|
|
2762
3504
|
cwd: process.cwd(),
|
|
2763
3505
|
taskSlug: flags.taskSlug,
|
|
@@ -2774,6 +3516,38 @@ var commandMap = {
|
|
|
2774
3516
|
cwd: process.cwd(),
|
|
2775
3517
|
...flags
|
|
2776
3518
|
});
|
|
3519
|
+
},
|
|
3520
|
+
lint: async (args) => {
|
|
3521
|
+
if (args[0] === "skills") {
|
|
3522
|
+
const { runLinter } = await import("../skill-frontmatter-linter-FMJADOK4.js");
|
|
3523
|
+
const { errors, filesScanned } = runLinter(process.cwd());
|
|
3524
|
+
console.log(`Scanned ${filesScanned} skill files.`);
|
|
3525
|
+
if (errors.length > 0) {
|
|
3526
|
+
console.error(`\\nFound ${errors.length} frontmatter issues:`);
|
|
3527
|
+
for (const err of errors) {
|
|
3528
|
+
console.error(`- ${err.filePath}: ${err.message}`);
|
|
3529
|
+
}
|
|
3530
|
+
process.exit(1);
|
|
3531
|
+
} else {
|
|
3532
|
+
console.log("All skills passed frontmatter linting.");
|
|
3533
|
+
}
|
|
3534
|
+
} else {
|
|
3535
|
+
console.error("Unknown lint target. Supported: skills");
|
|
3536
|
+
process.exit(1);
|
|
3537
|
+
}
|
|
3538
|
+
},
|
|
3539
|
+
skill: async (args) => {
|
|
3540
|
+
if (args[0] === "create") {
|
|
3541
|
+
const { parseSkillCreateArgs, runSkillCreate } = await import("../skill-4MEGJ3DO.js");
|
|
3542
|
+
const createOptions = parseSkillCreateArgs(args.slice(1));
|
|
3543
|
+
await runSkillCreate({
|
|
3544
|
+
cwd: process.cwd(),
|
|
3545
|
+
...createOptions
|
|
3546
|
+
});
|
|
3547
|
+
} else {
|
|
3548
|
+
console.error("Unknown skill command. Supported: create");
|
|
3549
|
+
process.exit(1);
|
|
3550
|
+
}
|
|
2777
3551
|
}
|
|
2778
3552
|
};
|
|
2779
3553
|
async function runCli(args) {
|