@rpamis/comet 0.4.0-beta.5 → 0.4.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/assets/manifest.json +1 -1
- package/assets/skills/comet/SKILL.md +1 -1
- package/assets/skills/comet/reference/comet-yaml-fields.md +4 -2
- package/assets/skills/comet/scripts/comet-runtime.mjs +504 -260
- package/assets/skills/comet-archive/SKILL.md +6 -7
- package/assets/skills/comet-build/SKILL.md +16 -10
- package/assets/skills/comet-hotfix/SKILL.md +16 -2
- package/assets/skills/comet-tweak/SKILL.md +15 -3
- package/assets/skills/comet-verify/SKILL.md +2 -0
- package/assets/skills-zh/comet/SKILL.md +1 -1
- package/assets/skills-zh/comet/reference/comet-yaml-fields.md +4 -2
- package/assets/skills-zh/comet-archive/SKILL.md +6 -7
- package/assets/skills-zh/comet-build/SKILL.md +16 -10
- package/assets/skills-zh/comet-hotfix/SKILL.md +16 -2
- package/assets/skills-zh/comet-tweak/SKILL.md +15 -3
- package/assets/skills-zh/comet-verify/SKILL.md +2 -0
- package/dist/app/commands/status.d.ts +1 -0
- package/dist/app/commands/status.d.ts.map +1 -1
- package/dist/app/commands/status.js +10 -0
- package/dist/app/commands/status.js.map +1 -1
- package/dist/domains/comet-classic/classic-branch-binding.d.ts +51 -0
- package/dist/domains/comet-classic/classic-branch-binding.d.ts.map +1 -0
- package/dist/domains/comet-classic/classic-branch-binding.js +107 -0
- package/dist/domains/comet-classic/classic-branch-binding.js.map +1 -0
- package/dist/domains/comet-classic/classic-current-change.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-current-change.js +37 -20
- package/dist/domains/comet-classic/classic-current-change.js.map +1 -1
- package/dist/domains/comet-classic/classic-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-guard.js +40 -10
- package/dist/domains/comet-classic/classic-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-hook-guard.js +25 -2
- package/dist/domains/comet-classic/classic-hook-guard.js.map +1 -1
- package/dist/domains/comet-classic/classic-state-command.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-state-command.js +94 -4
- package/dist/domains/comet-classic/classic-state-command.js.map +1 -1
- package/dist/domains/comet-classic/classic-state-events.d.ts +1 -1
- package/dist/domains/comet-classic/classic-state-events.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-state.d.ts +2 -1
- package/dist/domains/comet-classic/classic-state.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-state.js +3 -0
- package/dist/domains/comet-classic/classic-state.js.map +1 -1
- package/dist/domains/comet-classic/classic-transitions.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-transitions.js +1 -0
- package/dist/domains/comet-classic/classic-transitions.js.map +1 -1
- package/dist/domains/comet-classic/classic-validate-command.d.ts.map +1 -1
- package/dist/domains/comet-classic/classic-validate-command.js +6 -0
- package/dist/domains/comet-classic/classic-validate-command.js.map +1 -1
- package/dist/domains/skill/find.d.ts.map +1 -1
- package/dist/domains/skill/find.js +7 -1
- package/dist/domains/skill/find.js.map +1 -1
- package/package.json +1 -1
|
@@ -126,17 +126,17 @@ var require_visit = __commonJS({
|
|
|
126
126
|
visit.BREAK = BREAK;
|
|
127
127
|
visit.SKIP = SKIP;
|
|
128
128
|
visit.REMOVE = REMOVE;
|
|
129
|
-
function visit_(key, node, visitor,
|
|
130
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
129
|
+
function visit_(key, node, visitor, path23) {
|
|
130
|
+
const ctrl = callVisitor(key, node, visitor, path23);
|
|
131
131
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
132
|
-
replaceNode(key,
|
|
133
|
-
return visit_(key, ctrl, visitor,
|
|
132
|
+
replaceNode(key, path23, ctrl);
|
|
133
|
+
return visit_(key, ctrl, visitor, path23);
|
|
134
134
|
}
|
|
135
135
|
if (typeof ctrl !== "symbol") {
|
|
136
136
|
if (identity.isCollection(node)) {
|
|
137
|
-
|
|
137
|
+
path23 = Object.freeze(path23.concat(node));
|
|
138
138
|
for (let i = 0; i < node.items.length; ++i) {
|
|
139
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
139
|
+
const ci = visit_(i, node.items[i], visitor, path23);
|
|
140
140
|
if (typeof ci === "number")
|
|
141
141
|
i = ci - 1;
|
|
142
142
|
else if (ci === BREAK)
|
|
@@ -147,13 +147,13 @@ var require_visit = __commonJS({
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
} else if (identity.isPair(node)) {
|
|
150
|
-
|
|
151
|
-
const ck = visit_("key", node.key, visitor,
|
|
150
|
+
path23 = Object.freeze(path23.concat(node));
|
|
151
|
+
const ck = visit_("key", node.key, visitor, path23);
|
|
152
152
|
if (ck === BREAK)
|
|
153
153
|
return BREAK;
|
|
154
154
|
else if (ck === REMOVE)
|
|
155
155
|
node.key = null;
|
|
156
|
-
const cv = visit_("value", node.value, visitor,
|
|
156
|
+
const cv = visit_("value", node.value, visitor, path23);
|
|
157
157
|
if (cv === BREAK)
|
|
158
158
|
return BREAK;
|
|
159
159
|
else if (cv === REMOVE)
|
|
@@ -174,17 +174,17 @@ var require_visit = __commonJS({
|
|
|
174
174
|
visitAsync.BREAK = BREAK;
|
|
175
175
|
visitAsync.SKIP = SKIP;
|
|
176
176
|
visitAsync.REMOVE = REMOVE;
|
|
177
|
-
async function visitAsync_(key, node, visitor,
|
|
178
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
177
|
+
async function visitAsync_(key, node, visitor, path23) {
|
|
178
|
+
const ctrl = await callVisitor(key, node, visitor, path23);
|
|
179
179
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
180
|
-
replaceNode(key,
|
|
181
|
-
return visitAsync_(key, ctrl, visitor,
|
|
180
|
+
replaceNode(key, path23, ctrl);
|
|
181
|
+
return visitAsync_(key, ctrl, visitor, path23);
|
|
182
182
|
}
|
|
183
183
|
if (typeof ctrl !== "symbol") {
|
|
184
184
|
if (identity.isCollection(node)) {
|
|
185
|
-
|
|
185
|
+
path23 = Object.freeze(path23.concat(node));
|
|
186
186
|
for (let i = 0; i < node.items.length; ++i) {
|
|
187
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
187
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path23);
|
|
188
188
|
if (typeof ci === "number")
|
|
189
189
|
i = ci - 1;
|
|
190
190
|
else if (ci === BREAK)
|
|
@@ -195,13 +195,13 @@ var require_visit = __commonJS({
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
} else if (identity.isPair(node)) {
|
|
198
|
-
|
|
199
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
198
|
+
path23 = Object.freeze(path23.concat(node));
|
|
199
|
+
const ck = await visitAsync_("key", node.key, visitor, path23);
|
|
200
200
|
if (ck === BREAK)
|
|
201
201
|
return BREAK;
|
|
202
202
|
else if (ck === REMOVE)
|
|
203
203
|
node.key = null;
|
|
204
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
204
|
+
const cv = await visitAsync_("value", node.value, visitor, path23);
|
|
205
205
|
if (cv === BREAK)
|
|
206
206
|
return BREAK;
|
|
207
207
|
else if (cv === REMOVE)
|
|
@@ -228,23 +228,23 @@ var require_visit = __commonJS({
|
|
|
228
228
|
}
|
|
229
229
|
return visitor;
|
|
230
230
|
}
|
|
231
|
-
function callVisitor(key, node, visitor,
|
|
231
|
+
function callVisitor(key, node, visitor, path23) {
|
|
232
232
|
if (typeof visitor === "function")
|
|
233
|
-
return visitor(key, node,
|
|
233
|
+
return visitor(key, node, path23);
|
|
234
234
|
if (identity.isMap(node))
|
|
235
|
-
return visitor.Map?.(key, node,
|
|
235
|
+
return visitor.Map?.(key, node, path23);
|
|
236
236
|
if (identity.isSeq(node))
|
|
237
|
-
return visitor.Seq?.(key, node,
|
|
237
|
+
return visitor.Seq?.(key, node, path23);
|
|
238
238
|
if (identity.isPair(node))
|
|
239
|
-
return visitor.Pair?.(key, node,
|
|
239
|
+
return visitor.Pair?.(key, node, path23);
|
|
240
240
|
if (identity.isScalar(node))
|
|
241
|
-
return visitor.Scalar?.(key, node,
|
|
241
|
+
return visitor.Scalar?.(key, node, path23);
|
|
242
242
|
if (identity.isAlias(node))
|
|
243
|
-
return visitor.Alias?.(key, node,
|
|
243
|
+
return visitor.Alias?.(key, node, path23);
|
|
244
244
|
return void 0;
|
|
245
245
|
}
|
|
246
|
-
function replaceNode(key,
|
|
247
|
-
const parent =
|
|
246
|
+
function replaceNode(key, path23, node) {
|
|
247
|
+
const parent = path23[path23.length - 1];
|
|
248
248
|
if (identity.isCollection(parent)) {
|
|
249
249
|
parent.items[key] = node;
|
|
250
250
|
} else if (identity.isPair(parent)) {
|
|
@@ -854,10 +854,10 @@ var require_Collection = __commonJS({
|
|
|
854
854
|
var createNode = require_createNode();
|
|
855
855
|
var identity = require_identity();
|
|
856
856
|
var Node = require_Node();
|
|
857
|
-
function collectionFromPath(schema,
|
|
857
|
+
function collectionFromPath(schema, path23, value) {
|
|
858
858
|
let v = value;
|
|
859
|
-
for (let i =
|
|
860
|
-
const k =
|
|
859
|
+
for (let i = path23.length - 1; i >= 0; --i) {
|
|
860
|
+
const k = path23[i];
|
|
861
861
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
862
862
|
const a = [];
|
|
863
863
|
a[k] = v;
|
|
@@ -876,7 +876,7 @@ var require_Collection = __commonJS({
|
|
|
876
876
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
877
877
|
});
|
|
878
878
|
}
|
|
879
|
-
var isEmptyPath = (
|
|
879
|
+
var isEmptyPath = (path23) => path23 == null || typeof path23 === "object" && !!path23[Symbol.iterator]().next().done;
|
|
880
880
|
var Collection = class extends Node.NodeBase {
|
|
881
881
|
constructor(type, schema) {
|
|
882
882
|
super(type);
|
|
@@ -906,11 +906,11 @@ var require_Collection = __commonJS({
|
|
|
906
906
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
907
907
|
* that already exists in the map.
|
|
908
908
|
*/
|
|
909
|
-
addIn(
|
|
910
|
-
if (isEmptyPath(
|
|
909
|
+
addIn(path23, value) {
|
|
910
|
+
if (isEmptyPath(path23))
|
|
911
911
|
this.add(value);
|
|
912
912
|
else {
|
|
913
|
-
const [key, ...rest] =
|
|
913
|
+
const [key, ...rest] = path23;
|
|
914
914
|
const node = this.get(key, true);
|
|
915
915
|
if (identity.isCollection(node))
|
|
916
916
|
node.addIn(rest, value);
|
|
@@ -924,8 +924,8 @@ var require_Collection = __commonJS({
|
|
|
924
924
|
* Removes a value from the collection.
|
|
925
925
|
* @returns `true` if the item was found and removed.
|
|
926
926
|
*/
|
|
927
|
-
deleteIn(
|
|
928
|
-
const [key, ...rest] =
|
|
927
|
+
deleteIn(path23) {
|
|
928
|
+
const [key, ...rest] = path23;
|
|
929
929
|
if (rest.length === 0)
|
|
930
930
|
return this.delete(key);
|
|
931
931
|
const node = this.get(key, true);
|
|
@@ -939,8 +939,8 @@ var require_Collection = __commonJS({
|
|
|
939
939
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
940
940
|
* `true` (collections are always returned intact).
|
|
941
941
|
*/
|
|
942
|
-
getIn(
|
|
943
|
-
const [key, ...rest] =
|
|
942
|
+
getIn(path23, keepScalar) {
|
|
943
|
+
const [key, ...rest] = path23;
|
|
944
944
|
const node = this.get(key, true);
|
|
945
945
|
if (rest.length === 0)
|
|
946
946
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -958,8 +958,8 @@ var require_Collection = __commonJS({
|
|
|
958
958
|
/**
|
|
959
959
|
* Checks if the collection includes a value with the key `key`.
|
|
960
960
|
*/
|
|
961
|
-
hasIn(
|
|
962
|
-
const [key, ...rest] =
|
|
961
|
+
hasIn(path23) {
|
|
962
|
+
const [key, ...rest] = path23;
|
|
963
963
|
if (rest.length === 0)
|
|
964
964
|
return this.has(key);
|
|
965
965
|
const node = this.get(key, true);
|
|
@@ -969,8 +969,8 @@ var require_Collection = __commonJS({
|
|
|
969
969
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
970
970
|
* boolean to add/remove the item from the set.
|
|
971
971
|
*/
|
|
972
|
-
setIn(
|
|
973
|
-
const [key, ...rest] =
|
|
972
|
+
setIn(path23, value) {
|
|
973
|
+
const [key, ...rest] = path23;
|
|
974
974
|
if (rest.length === 0) {
|
|
975
975
|
this.set(key, value);
|
|
976
976
|
} else {
|
|
@@ -3485,9 +3485,9 @@ var require_Document = __commonJS({
|
|
|
3485
3485
|
this.contents.add(value);
|
|
3486
3486
|
}
|
|
3487
3487
|
/** Adds a value to the document. */
|
|
3488
|
-
addIn(
|
|
3488
|
+
addIn(path23, value) {
|
|
3489
3489
|
if (assertCollection(this.contents))
|
|
3490
|
-
this.contents.addIn(
|
|
3490
|
+
this.contents.addIn(path23, value);
|
|
3491
3491
|
}
|
|
3492
3492
|
/**
|
|
3493
3493
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3562,14 +3562,14 @@ var require_Document = __commonJS({
|
|
|
3562
3562
|
* Removes a value from the document.
|
|
3563
3563
|
* @returns `true` if the item was found and removed.
|
|
3564
3564
|
*/
|
|
3565
|
-
deleteIn(
|
|
3566
|
-
if (Collection.isEmptyPath(
|
|
3565
|
+
deleteIn(path23) {
|
|
3566
|
+
if (Collection.isEmptyPath(path23)) {
|
|
3567
3567
|
if (this.contents == null)
|
|
3568
3568
|
return false;
|
|
3569
3569
|
this.contents = null;
|
|
3570
3570
|
return true;
|
|
3571
3571
|
}
|
|
3572
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3572
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path23) : false;
|
|
3573
3573
|
}
|
|
3574
3574
|
/**
|
|
3575
3575
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3584,10 +3584,10 @@ var require_Document = __commonJS({
|
|
|
3584
3584
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3585
3585
|
* `true` (collections are always returned intact).
|
|
3586
3586
|
*/
|
|
3587
|
-
getIn(
|
|
3588
|
-
if (Collection.isEmptyPath(
|
|
3587
|
+
getIn(path23, keepScalar) {
|
|
3588
|
+
if (Collection.isEmptyPath(path23))
|
|
3589
3589
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3590
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3590
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path23, keepScalar) : void 0;
|
|
3591
3591
|
}
|
|
3592
3592
|
/**
|
|
3593
3593
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3598,10 +3598,10 @@ var require_Document = __commonJS({
|
|
|
3598
3598
|
/**
|
|
3599
3599
|
* Checks if the document includes a value at `path`.
|
|
3600
3600
|
*/
|
|
3601
|
-
hasIn(
|
|
3602
|
-
if (Collection.isEmptyPath(
|
|
3601
|
+
hasIn(path23) {
|
|
3602
|
+
if (Collection.isEmptyPath(path23))
|
|
3603
3603
|
return this.contents !== void 0;
|
|
3604
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3604
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path23) : false;
|
|
3605
3605
|
}
|
|
3606
3606
|
/**
|
|
3607
3607
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3618,13 +3618,13 @@ var require_Document = __commonJS({
|
|
|
3618
3618
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3619
3619
|
* boolean to add/remove the item from the set.
|
|
3620
3620
|
*/
|
|
3621
|
-
setIn(
|
|
3622
|
-
if (Collection.isEmptyPath(
|
|
3621
|
+
setIn(path23, value) {
|
|
3622
|
+
if (Collection.isEmptyPath(path23)) {
|
|
3623
3623
|
this.contents = value;
|
|
3624
3624
|
} else if (this.contents == null) {
|
|
3625
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3625
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path23), value);
|
|
3626
3626
|
} else if (assertCollection(this.contents)) {
|
|
3627
|
-
this.contents.setIn(
|
|
3627
|
+
this.contents.setIn(path23, value);
|
|
3628
3628
|
}
|
|
3629
3629
|
}
|
|
3630
3630
|
/**
|
|
@@ -5584,9 +5584,9 @@ var require_cst_visit = __commonJS({
|
|
|
5584
5584
|
visit.BREAK = BREAK;
|
|
5585
5585
|
visit.SKIP = SKIP;
|
|
5586
5586
|
visit.REMOVE = REMOVE;
|
|
5587
|
-
visit.itemAtPath = (cst,
|
|
5587
|
+
visit.itemAtPath = (cst, path23) => {
|
|
5588
5588
|
let item = cst;
|
|
5589
|
-
for (const [field2, index] of
|
|
5589
|
+
for (const [field2, index] of path23) {
|
|
5590
5590
|
const tok = item?.[field2];
|
|
5591
5591
|
if (tok && "items" in tok) {
|
|
5592
5592
|
item = tok.items[index];
|
|
@@ -5595,23 +5595,23 @@ var require_cst_visit = __commonJS({
|
|
|
5595
5595
|
}
|
|
5596
5596
|
return item;
|
|
5597
5597
|
};
|
|
5598
|
-
visit.parentCollection = (cst,
|
|
5599
|
-
const parent = visit.itemAtPath(cst,
|
|
5600
|
-
const field2 =
|
|
5598
|
+
visit.parentCollection = (cst, path23) => {
|
|
5599
|
+
const parent = visit.itemAtPath(cst, path23.slice(0, -1));
|
|
5600
|
+
const field2 = path23[path23.length - 1][0];
|
|
5601
5601
|
const coll = parent?.[field2];
|
|
5602
5602
|
if (coll && "items" in coll)
|
|
5603
5603
|
return coll;
|
|
5604
5604
|
throw new Error("Parent collection not found");
|
|
5605
5605
|
};
|
|
5606
|
-
function _visit(
|
|
5607
|
-
let ctrl = visitor(item,
|
|
5606
|
+
function _visit(path23, item, visitor) {
|
|
5607
|
+
let ctrl = visitor(item, path23);
|
|
5608
5608
|
if (typeof ctrl === "symbol")
|
|
5609
5609
|
return ctrl;
|
|
5610
5610
|
for (const field2 of ["key", "value"]) {
|
|
5611
5611
|
const token = item[field2];
|
|
5612
5612
|
if (token && "items" in token) {
|
|
5613
5613
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5614
|
-
const ci = _visit(Object.freeze(
|
|
5614
|
+
const ci = _visit(Object.freeze(path23.concat([[field2, i]])), token.items[i], visitor);
|
|
5615
5615
|
if (typeof ci === "number")
|
|
5616
5616
|
i = ci - 1;
|
|
5617
5617
|
else if (ci === BREAK)
|
|
@@ -5622,10 +5622,10 @@ var require_cst_visit = __commonJS({
|
|
|
5622
5622
|
}
|
|
5623
5623
|
}
|
|
5624
5624
|
if (typeof ctrl === "function" && field2 === "key")
|
|
5625
|
-
ctrl = ctrl(item,
|
|
5625
|
+
ctrl = ctrl(item, path23);
|
|
5626
5626
|
}
|
|
5627
5627
|
}
|
|
5628
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5628
|
+
return typeof ctrl === "function" ? ctrl(item, path23) : ctrl;
|
|
5629
5629
|
}
|
|
5630
5630
|
exports.visit = visit;
|
|
5631
5631
|
}
|
|
@@ -6927,14 +6927,14 @@ var require_parser = __commonJS({
|
|
|
6927
6927
|
case "scalar":
|
|
6928
6928
|
case "single-quoted-scalar":
|
|
6929
6929
|
case "double-quoted-scalar": {
|
|
6930
|
-
const
|
|
6930
|
+
const fs22 = this.flowScalar(this.type);
|
|
6931
6931
|
if (atNextItem || it.value) {
|
|
6932
|
-
map.items.push({ start, key:
|
|
6932
|
+
map.items.push({ start, key: fs22, sep: [] });
|
|
6933
6933
|
this.onKeyLine = true;
|
|
6934
6934
|
} else if (it.sep) {
|
|
6935
|
-
this.stack.push(
|
|
6935
|
+
this.stack.push(fs22);
|
|
6936
6936
|
} else {
|
|
6937
|
-
Object.assign(it, { key:
|
|
6937
|
+
Object.assign(it, { key: fs22, sep: [] });
|
|
6938
6938
|
this.onKeyLine = true;
|
|
6939
6939
|
}
|
|
6940
6940
|
return;
|
|
@@ -7062,13 +7062,13 @@ var require_parser = __commonJS({
|
|
|
7062
7062
|
case "scalar":
|
|
7063
7063
|
case "single-quoted-scalar":
|
|
7064
7064
|
case "double-quoted-scalar": {
|
|
7065
|
-
const
|
|
7065
|
+
const fs22 = this.flowScalar(this.type);
|
|
7066
7066
|
if (!it || it.value)
|
|
7067
|
-
fc.items.push({ start: [], key:
|
|
7067
|
+
fc.items.push({ start: [], key: fs22, sep: [] });
|
|
7068
7068
|
else if (it.sep)
|
|
7069
|
-
this.stack.push(
|
|
7069
|
+
this.stack.push(fs22);
|
|
7070
7070
|
else
|
|
7071
|
-
Object.assign(it, { key:
|
|
7071
|
+
Object.assign(it, { key: fs22, sep: [] });
|
|
7072
7072
|
return;
|
|
7073
7073
|
}
|
|
7074
7074
|
case "flow-map-end":
|
|
@@ -7257,7 +7257,7 @@ var require_public_api = __commonJS({
|
|
|
7257
7257
|
return docs;
|
|
7258
7258
|
return Object.assign([], { empty: true }, composer$1.streamInfo());
|
|
7259
7259
|
}
|
|
7260
|
-
function
|
|
7260
|
+
function parseDocument8(source, options = {}) {
|
|
7261
7261
|
const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
|
|
7262
7262
|
const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
|
|
7263
7263
|
const composer$1 = new composer.Composer(options);
|
|
@@ -7283,7 +7283,7 @@ var require_public_api = __commonJS({
|
|
|
7283
7283
|
} else if (options === void 0 && reviver && typeof reviver === "object") {
|
|
7284
7284
|
options = reviver;
|
|
7285
7285
|
}
|
|
7286
|
-
const doc =
|
|
7286
|
+
const doc = parseDocument8(src, options);
|
|
7287
7287
|
if (!doc)
|
|
7288
7288
|
return null;
|
|
7289
7289
|
doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
|
|
@@ -7319,7 +7319,7 @@ var require_public_api = __commonJS({
|
|
|
7319
7319
|
}
|
|
7320
7320
|
exports.parse = parse2;
|
|
7321
7321
|
exports.parseAllDocuments = parseAllDocuments;
|
|
7322
|
-
exports.parseDocument =
|
|
7322
|
+
exports.parseDocument = parseDocument8;
|
|
7323
7323
|
exports.stringify = stringify;
|
|
7324
7324
|
}
|
|
7325
7325
|
});
|
|
@@ -7824,6 +7824,7 @@ var CLASSIC_WIRE_KEYS = [
|
|
|
7824
7824
|
"tdd_mode",
|
|
7825
7825
|
"review_mode",
|
|
7826
7826
|
"isolation",
|
|
7827
|
+
"bound_branch",
|
|
7827
7828
|
"verify_mode",
|
|
7828
7829
|
"auto_transition",
|
|
7829
7830
|
"base_ref",
|
|
@@ -7942,6 +7943,7 @@ function classicStateFromDocument(doc) {
|
|
|
7942
7943
|
tddMode: enumValue(doc, "tdd_mode", TDD_MODES),
|
|
7943
7944
|
reviewMode: enumValue(doc, "review_mode", REVIEW_MODES),
|
|
7944
7945
|
isolation: enumValue(doc, "isolation", ISOLATIONS),
|
|
7946
|
+
boundBranch: nullableString(doc, "bound_branch"),
|
|
7945
7947
|
verifyMode: enumValue(doc, "verify_mode", VERIFY_MODES),
|
|
7946
7948
|
autoTransition: booleanValue(doc, "auto_transition"),
|
|
7947
7949
|
baseRef: nullableString(doc, "base_ref"),
|
|
@@ -8007,6 +8009,7 @@ function classicStateToDocument(state) {
|
|
|
8007
8009
|
tdd_mode: state.tddMode,
|
|
8008
8010
|
review_mode: state.reviewMode,
|
|
8009
8011
|
isolation: state.isolation,
|
|
8012
|
+
bound_branch: state.boundBranch,
|
|
8010
8013
|
verify_mode: state.verifyMode,
|
|
8011
8014
|
auto_transition: state.autoTransition,
|
|
8012
8015
|
base_ref: state.baseRef,
|
|
@@ -9493,6 +9496,7 @@ function applyClassicTransition(current, event, options = {}) {
|
|
|
9493
9496
|
setField(classic, effects, "tddMode", null);
|
|
9494
9497
|
setField(classic, effects, "reviewMode", null);
|
|
9495
9498
|
setField(classic, effects, "isolation", null);
|
|
9499
|
+
setField(classic, effects, "boundBranch", null);
|
|
9496
9500
|
setField(classic, effects, "verifyMode", null);
|
|
9497
9501
|
setField(classic, effects, "directOverride", null);
|
|
9498
9502
|
} else if (event === "archive-confirm") {
|
|
@@ -9907,11 +9911,11 @@ var classicArchiveCommand = async (args) => {
|
|
|
9907
9911
|
};
|
|
9908
9912
|
|
|
9909
9913
|
// domains/comet-classic/classic-guard.ts
|
|
9910
|
-
var
|
|
9914
|
+
var import_yaml6 = __toESM(require_dist(), 1);
|
|
9911
9915
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9912
9916
|
import { createHash as createHash4 } from "crypto";
|
|
9913
|
-
import { existsSync, promises as
|
|
9914
|
-
import
|
|
9917
|
+
import { existsSync, promises as fs16, readFileSync } from "fs";
|
|
9918
|
+
import path17 from "path";
|
|
9915
9919
|
|
|
9916
9920
|
// domains/comet-classic/classic-command-checks.ts
|
|
9917
9921
|
import path13 from "path";
|
|
@@ -10213,6 +10217,12 @@ var classicValidateCommand = async (args) => {
|
|
|
10213
10217
|
fail3(`${field2}='${value}' is not valid. Expected: ${values.join(" ")}`);
|
|
10214
10218
|
}
|
|
10215
10219
|
}
|
|
10220
|
+
if (Object.prototype.hasOwnProperty.call(record, "bound_branch")) {
|
|
10221
|
+
const value = record.bound_branch;
|
|
10222
|
+
if (value !== null && typeof value !== "string") {
|
|
10223
|
+
fail3(`bound_branch='${text(value)}' is not a string or null`);
|
|
10224
|
+
}
|
|
10225
|
+
}
|
|
10216
10226
|
if (Object.prototype.hasOwnProperty.call(record, "verify_failures")) {
|
|
10217
10227
|
const value = record.verify_failures;
|
|
10218
10228
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
@@ -10303,6 +10313,96 @@ async function readClassicConfigValue(field2, options = {}) {
|
|
|
10303
10313
|
return null;
|
|
10304
10314
|
}
|
|
10305
10315
|
|
|
10316
|
+
// domains/comet-classic/classic-branch-binding.ts
|
|
10317
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
10318
|
+
import { execFileSync } from "child_process";
|
|
10319
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
10320
|
+
import { promises as fs15 } from "fs";
|
|
10321
|
+
import path16 from "path";
|
|
10322
|
+
function liveGitBranch(cwd) {
|
|
10323
|
+
try {
|
|
10324
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
10325
|
+
cwd,
|
|
10326
|
+
encoding: "utf8",
|
|
10327
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
10328
|
+
}).trim();
|
|
10329
|
+
return branch && branch !== "HEAD" ? branch : null;
|
|
10330
|
+
} catch {
|
|
10331
|
+
return null;
|
|
10332
|
+
}
|
|
10333
|
+
}
|
|
10334
|
+
function isGitWorkTree(cwd) {
|
|
10335
|
+
try {
|
|
10336
|
+
return execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
10337
|
+
cwd,
|
|
10338
|
+
encoding: "utf8",
|
|
10339
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
10340
|
+
}).trim() === "true";
|
|
10341
|
+
} catch {
|
|
10342
|
+
return false;
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
var BOUND_BRANCH_ISOLATIONS = ["current", "branch", "worktree"];
|
|
10346
|
+
function requiresBranchBinding(isolation) {
|
|
10347
|
+
return BOUND_BRANCH_ISOLATIONS.includes(isolation);
|
|
10348
|
+
}
|
|
10349
|
+
function evaluateBranchBinding(input) {
|
|
10350
|
+
if (!requiresBranchBinding(input.isolation)) return { status: "not-applicable" };
|
|
10351
|
+
if (input.boundBranch === null && input.currentBranch === null && input.gitWorkTree === false) {
|
|
10352
|
+
return { status: "not-applicable" };
|
|
10353
|
+
}
|
|
10354
|
+
if (input.boundBranch === null) {
|
|
10355
|
+
return input.currentBranch === null ? { status: "unbound-detached" } : { status: "needs-heal", branch: input.currentBranch };
|
|
10356
|
+
}
|
|
10357
|
+
if (input.currentBranch === input.boundBranch) return { status: "ok" };
|
|
10358
|
+
return { status: "drift", boundBranch: input.boundBranch, currentBranch: input.currentBranch };
|
|
10359
|
+
}
|
|
10360
|
+
async function resolveBranchBinding(changeDir, options) {
|
|
10361
|
+
const file = path16.join(changeDir, ".comet.yaml");
|
|
10362
|
+
const document = (0, import_yaml5.parseDocument)(await fs15.readFile(file, "utf8"), { uniqueKeys: false });
|
|
10363
|
+
if (document.errors.length > 0) {
|
|
10364
|
+
throw new Error(`Invalid .comet.yaml: ${document.errors[0].message}`);
|
|
10365
|
+
}
|
|
10366
|
+
const record = document.toJS() ?? {};
|
|
10367
|
+
const isolation = typeof record.isolation === "string" ? record.isolation : null;
|
|
10368
|
+
const boundBranch = typeof record.bound_branch === "string" && record.bound_branch !== "" ? record.bound_branch : null;
|
|
10369
|
+
const bindingRequired = requiresBranchBinding(isolation);
|
|
10370
|
+
const currentBranch = liveGitBranch(options.cwd);
|
|
10371
|
+
const gitWorkTree = bindingRequired && boundBranch === null && currentBranch === null ? isGitWorkTree(options.cwd) : true;
|
|
10372
|
+
const verdict = evaluateBranchBinding({ isolation, boundBranch, currentBranch, gitWorkTree });
|
|
10373
|
+
if (verdict.status === "needs-heal" && options.heal) {
|
|
10374
|
+
await healBoundBranch(changeDir, verdict.branch);
|
|
10375
|
+
return { status: "healed", branch: verdict.branch, bindingRequired, currentBranch };
|
|
10376
|
+
}
|
|
10377
|
+
return { ...verdict, bindingRequired, currentBranch };
|
|
10378
|
+
}
|
|
10379
|
+
async function healBoundBranch(changeDir, branch) {
|
|
10380
|
+
const file = path16.join(changeDir, ".comet.yaml");
|
|
10381
|
+
const document = (0, import_yaml5.parseDocument)(await fs15.readFile(file, "utf8"), { uniqueKeys: false });
|
|
10382
|
+
document.set("bound_branch", branch);
|
|
10383
|
+
const temporary = `${file}.${randomUUID6()}.tmp`;
|
|
10384
|
+
try {
|
|
10385
|
+
await fs15.writeFile(temporary, document.toString(), "utf8");
|
|
10386
|
+
await fs15.rename(temporary, file);
|
|
10387
|
+
} catch (error) {
|
|
10388
|
+
await fs15.rm(temporary, { force: true });
|
|
10389
|
+
throw error;
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
function branchLabel(currentBranch) {
|
|
10393
|
+
return currentBranch ?? "detached HEAD";
|
|
10394
|
+
}
|
|
10395
|
+
function driftBlockedMessage(change, boundBranch, currentBranch) {
|
|
10396
|
+
return `change '${change}' is bound to branch '${boundBranch}', but current branch is '${branchLabel(currentBranch)}'.
|
|
10397
|
+
Next: ask the user to confirm — switch back to '${boundBranch}', or run \`comet state rebind ${change}\` after explicit confirmation.`;
|
|
10398
|
+
}
|
|
10399
|
+
function driftStaleReason(change, boundBranch, currentBranch) {
|
|
10400
|
+
return `change '${change}' is bound to branch '${boundBranch}', but current branch is '${branchLabel(currentBranch)}'`;
|
|
10401
|
+
}
|
|
10402
|
+
function unboundDetachedMessage(change) {
|
|
10403
|
+
return `change '${change}' uses a branch-bound workspace mode but has no bound branch and HEAD is detached; checkout a branch first before continuing.`;
|
|
10404
|
+
}
|
|
10405
|
+
|
|
10306
10406
|
// domains/comet-classic/classic-guard.ts
|
|
10307
10407
|
var GREEN3 = "\x1B[32m";
|
|
10308
10408
|
var RED3 = "\x1B[31m";
|
|
@@ -10367,7 +10467,7 @@ var GuardOutput = class {
|
|
|
10367
10467
|
};
|
|
10368
10468
|
async function exists4(file) {
|
|
10369
10469
|
try {
|
|
10370
|
-
await
|
|
10470
|
+
await fs16.access(file);
|
|
10371
10471
|
return true;
|
|
10372
10472
|
} catch (error) {
|
|
10373
10473
|
if (error.code === "ENOENT") return false;
|
|
@@ -10376,7 +10476,7 @@ async function exists4(file) {
|
|
|
10376
10476
|
}
|
|
10377
10477
|
async function nonempty(file) {
|
|
10378
10478
|
try {
|
|
10379
|
-
return (await
|
|
10479
|
+
return (await fs16.stat(file)).size > 0;
|
|
10380
10480
|
} catch (error) {
|
|
10381
10481
|
if (error.code === "ENOENT") return false;
|
|
10382
10482
|
throw error;
|
|
@@ -10390,8 +10490,8 @@ async function resolveChangeDir(name) {
|
|
|
10390
10490
|
return (await resolveClassicChangeDirectory(name)).label;
|
|
10391
10491
|
}
|
|
10392
10492
|
async function readField(changeDir, field2) {
|
|
10393
|
-
const file =
|
|
10394
|
-
const document = (0,
|
|
10493
|
+
const file = path17.join(changeDir, ".comet.yaml");
|
|
10494
|
+
const document = (0, import_yaml6.parseDocument)(await fs16.readFile(file, "utf8"), { uniqueKeys: false });
|
|
10395
10495
|
if (document.errors.length > 0) {
|
|
10396
10496
|
throw new GuardFailure(`ERROR: Invalid .comet.yaml: ${document.errors[0].message}`);
|
|
10397
10497
|
}
|
|
@@ -10432,7 +10532,7 @@ function countEnglishWords(source) {
|
|
|
10432
10532
|
}
|
|
10433
10533
|
async function documentLanguageMatchesConfigured(changeDir, file) {
|
|
10434
10534
|
const language = await configuredLanguage(changeDir);
|
|
10435
|
-
const source = stripFencedCodeBlocks(await
|
|
10535
|
+
const source = stripFencedCodeBlocks(await fs16.readFile(file, "utf8"));
|
|
10436
10536
|
const cjk = countCjkChars(source);
|
|
10437
10537
|
const englishWords = countEnglishWords(source);
|
|
10438
10538
|
if (language === "zh-CN" && cjk < 20 && englishWords >= 20) {
|
|
@@ -10456,7 +10556,7 @@ async function handoffSourceFiles(changeDir) {
|
|
|
10456
10556
|
const files = [`${changeDir}/proposal.md`, `${changeDir}/design.md`, `${changeDir}/tasks.md`];
|
|
10457
10557
|
const specs = `${changeDir}/specs`;
|
|
10458
10558
|
if (await exists4(specs)) {
|
|
10459
|
-
for (const entry2 of (await
|
|
10559
|
+
for (const entry2 of (await fs16.readdir(specs)).sort()) {
|
|
10460
10560
|
const spec = `${specs}/${entry2}/spec.md`;
|
|
10461
10561
|
if (await exists4(spec)) files.push(spec);
|
|
10462
10562
|
}
|
|
@@ -10476,7 +10576,7 @@ async function preflight(changeDir, name) {
|
|
|
10476
10576
|
if (!await exists4(changeDir)) {
|
|
10477
10577
|
throw new GuardFailure(red2(`FATAL: change directory not found: ${changeDir}`));
|
|
10478
10578
|
}
|
|
10479
|
-
if (!await exists4(
|
|
10579
|
+
if (!await exists4(path17.join(changeDir, ".comet.yaml"))) {
|
|
10480
10580
|
throw new GuardFailure(red2(`FATAL: .comet.yaml not found in ${changeDir}`));
|
|
10481
10581
|
}
|
|
10482
10582
|
const result5 = await classicValidateCommand([name], { json: false });
|
|
@@ -10546,9 +10646,9 @@ var INFERRED_COMMAND_SOURCES = [
|
|
|
10546
10646
|
"Cargo.toml"
|
|
10547
10647
|
];
|
|
10548
10648
|
async function removedProjectCommandField(field2) {
|
|
10549
|
-
const config =
|
|
10649
|
+
const config = path17.join(".comet", "config.yaml");
|
|
10550
10650
|
if (!await exists4(config)) return false;
|
|
10551
|
-
const document = (0,
|
|
10651
|
+
const document = (0, import_yaml6.parseDocument)(await fs16.readFile(config, "utf8"));
|
|
10552
10652
|
if (document.errors.length > 0) {
|
|
10553
10653
|
throw new Error(
|
|
10554
10654
|
`.comet/config.yaml is invalid YAML (${document.errors[0].message}); cannot check for removed "${field2}" field. Fix the config and retry.`
|
|
@@ -10629,14 +10729,14 @@ ${recoveryCommand(change, scope, recorded.command)}`
|
|
|
10629
10729
|
return { status: 0, output: evidenceDetail(recorded) };
|
|
10630
10730
|
}
|
|
10631
10731
|
async function tasksAllDone(changeDir) {
|
|
10632
|
-
const tasks =
|
|
10732
|
+
const tasks = path17.join(changeDir, "tasks.md");
|
|
10633
10733
|
if (!await exists4(tasks)) {
|
|
10634
10734
|
return fail(
|
|
10635
10735
|
`tasks.md is missing at ${tasks}
|
|
10636
10736
|
Next: restore or create tasks.md for this change before leaving build.`
|
|
10637
10737
|
);
|
|
10638
10738
|
}
|
|
10639
|
-
const source = await
|
|
10739
|
+
const source = await fs16.readFile(tasks, "utf8");
|
|
10640
10740
|
if (!/- \[x\]/u.test(source)) {
|
|
10641
10741
|
return fail(
|
|
10642
10742
|
"tasks.md has no completed tasks.\nNext: complete implementation tasks and mark them with '- [x]'."
|
|
@@ -10653,9 +10753,9 @@ Next: complete or explicitly remove unfinished tasks, then mark tasks.md with '-
|
|
|
10653
10753
|
return pass();
|
|
10654
10754
|
}
|
|
10655
10755
|
async function tasksHasAny(changeDir) {
|
|
10656
|
-
const tasks =
|
|
10756
|
+
const tasks = path17.join(changeDir, "tasks.md");
|
|
10657
10757
|
if (!await exists4(tasks)) return false;
|
|
10658
|
-
return /- \[/u.test(await
|
|
10758
|
+
return /- \[/u.test(await fs16.readFile(tasks, "utf8"));
|
|
10659
10759
|
}
|
|
10660
10760
|
async function planTasksAllDone(changeDir) {
|
|
10661
10761
|
const plan = await readField(changeDir, "plan");
|
|
@@ -10666,7 +10766,7 @@ async function planTasksAllDone(changeDir) {
|
|
|
10666
10766
|
Next: restore the Superpowers plan file or update .comet.yaml plan before leaving build.`
|
|
10667
10767
|
);
|
|
10668
10768
|
}
|
|
10669
|
-
const source = await
|
|
10769
|
+
const source = await fs16.readFile(plan, "utf8");
|
|
10670
10770
|
const unfinished = source.split(/\r?\n/u).map((line, index) => ({ line, number: index + 1 })).filter((entry2) => /^\s*- \[ \]/u.test(entry2.line));
|
|
10671
10771
|
if (unfinished.length > 0) {
|
|
10672
10772
|
return fail(
|
|
@@ -10677,14 +10777,36 @@ Next: check off corresponding completed plan tasks, then commit the plan update.
|
|
|
10677
10777
|
}
|
|
10678
10778
|
return pass();
|
|
10679
10779
|
}
|
|
10780
|
+
async function boundBranchMatches(changeDir, change) {
|
|
10781
|
+
let outcome;
|
|
10782
|
+
try {
|
|
10783
|
+
outcome = await resolveBranchBinding(changeDir, { heal: true, cwd: process.cwd() });
|
|
10784
|
+
} catch (error) {
|
|
10785
|
+
throw new GuardFailure(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
|
|
10786
|
+
}
|
|
10787
|
+
switch (outcome.status) {
|
|
10788
|
+
case "drift":
|
|
10789
|
+
return fail(driftBlockedMessage(change, outcome.boundBranch, outcome.currentBranch));
|
|
10790
|
+
case "unbound-detached":
|
|
10791
|
+
return fail(unboundDetachedMessage(change));
|
|
10792
|
+
case "healed":
|
|
10793
|
+
return pass(`bound_branch lazily set to ${outcome.branch}`);
|
|
10794
|
+
case "needs-heal":
|
|
10795
|
+
case "ok":
|
|
10796
|
+
case "not-applicable":
|
|
10797
|
+
return pass();
|
|
10798
|
+
default: {
|
|
10799
|
+
const exhaustive = outcome;
|
|
10800
|
+
throw new Error(`unhandled branch binding status: ${JSON.stringify(exhaustive)}`);
|
|
10801
|
+
}
|
|
10802
|
+
}
|
|
10803
|
+
}
|
|
10680
10804
|
async function isolationSelected(changeDir, change) {
|
|
10681
10805
|
const isolation = await readField(changeDir, "isolation");
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
if (isolation === "current" && (workflow === "hotfix" || workflow === "tweak")) return pass();
|
|
10685
|
-
const allowedValues = workflow === "full" ? "<branch|worktree>" : "<current|branch|worktree>";
|
|
10806
|
+
if (isolation === "current" || isolation === "branch" || isolation === "worktree") return pass();
|
|
10807
|
+
const allowedValues = "<current|branch|worktree>";
|
|
10686
10808
|
return fail(
|
|
10687
|
-
`isolation must be
|
|
10809
|
+
`isolation must be current, branch, or worktree, got '${isolation || "null"}'
|
|
10688
10810
|
Next: choose a valid workspace mode, prepare it when needed, then run:
|
|
10689
10811
|
comet state set ${change} isolation ${allowedValues}`
|
|
10690
10812
|
);
|
|
@@ -10758,7 +10880,7 @@ async function archivedIsTrue(changeDir) {
|
|
|
10758
10880
|
return await readField(changeDir, "archived") === "true";
|
|
10759
10881
|
}
|
|
10760
10882
|
async function designDocFrontmatterHas(designDoc, field2, expected) {
|
|
10761
|
-
const source = (await
|
|
10883
|
+
const source = (await fs16.readFile(designDoc, "utf8")).replace(/^\uFEFF/u, "");
|
|
10762
10884
|
let inFrontmatter = false;
|
|
10763
10885
|
for (const line of source.split(/\r?\n/u)) {
|
|
10764
10886
|
if (!inFrontmatter) {
|
|
@@ -10823,7 +10945,7 @@ async function designHandoffMarkdownTraceable(changeDir) {
|
|
|
10823
10945
|
const markdown = `${context.replace(/\.json$/u, "")}.md`;
|
|
10824
10946
|
if (!await nonempty(markdown))
|
|
10825
10947
|
return fail(`design handoff markdown is missing or empty: ${markdown}`);
|
|
10826
|
-
const source = await
|
|
10948
|
+
const source = await fs16.readFile(markdown, "utf8");
|
|
10827
10949
|
const problems = [];
|
|
10828
10950
|
if (!/^Generated-by: comet-handoff\.sh$/mu.test(source)) {
|
|
10829
10951
|
problems.push("handoff markdown is missing Generated-by marker");
|
|
@@ -10850,7 +10972,7 @@ async function betaSpecJsonStructurallyValid(changeDir) {
|
|
|
10850
10972
|
const context = await readField(changeDir, "handoff_context");
|
|
10851
10973
|
if (!context || context === "null") return fail("handoff_context is missing from .comet.yaml");
|
|
10852
10974
|
if (!await nonempty(context)) return fail(`spec-context.json is missing or empty: ${context}`);
|
|
10853
|
-
const source = await
|
|
10975
|
+
const source = await fs16.readFile(context, "utf8");
|
|
10854
10976
|
const problems = [];
|
|
10855
10977
|
let parsed;
|
|
10856
10978
|
try {
|
|
@@ -10887,19 +11009,19 @@ async function guardOpenChecks(output, changeDir) {
|
|
|
10887
11009
|
const checks = [
|
|
10888
11010
|
check(
|
|
10889
11011
|
"proposal.md exists and non-empty",
|
|
10890
|
-
async () => await nonempty(
|
|
11012
|
+
async () => await nonempty(path17.join(changeDir, "proposal.md")) ? pass() : fail("")
|
|
10891
11013
|
),
|
|
10892
11014
|
check(
|
|
10893
11015
|
"proposal.md matches configured language",
|
|
10894
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11016
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "proposal.md"))
|
|
10895
11017
|
),
|
|
10896
11018
|
check(
|
|
10897
11019
|
"tasks.md exists and non-empty",
|
|
10898
|
-
async () => await nonempty(
|
|
11020
|
+
async () => await nonempty(path17.join(changeDir, "tasks.md")) ? pass() : fail("")
|
|
10899
11021
|
),
|
|
10900
11022
|
check(
|
|
10901
11023
|
"tasks.md matches configured language",
|
|
10902
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11024
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "tasks.md"))
|
|
10903
11025
|
),
|
|
10904
11026
|
check(
|
|
10905
11027
|
"tasks.md has at least one task",
|
|
@@ -10912,11 +11034,11 @@ async function guardOpenChecks(output, changeDir) {
|
|
|
10912
11034
|
0,
|
|
10913
11035
|
check(
|
|
10914
11036
|
"design.md exists and non-empty",
|
|
10915
|
-
async () => await nonempty(
|
|
11037
|
+
async () => await nonempty(path17.join(changeDir, "design.md")) ? pass() : fail("")
|
|
10916
11038
|
),
|
|
10917
11039
|
check(
|
|
10918
11040
|
"design.md matches configured language",
|
|
10919
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11041
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "design.md"))
|
|
10920
11042
|
)
|
|
10921
11043
|
);
|
|
10922
11044
|
}
|
|
@@ -10928,27 +11050,27 @@ async function guardDesignChecks(output, changeDir, change) {
|
|
|
10928
11050
|
const builders = [
|
|
10929
11051
|
check(
|
|
10930
11052
|
"proposal.md exists",
|
|
10931
|
-
async () => await nonempty(
|
|
11053
|
+
async () => await nonempty(path17.join(changeDir, "proposal.md")) ? pass() : fail("")
|
|
10932
11054
|
),
|
|
10933
11055
|
check(
|
|
10934
11056
|
"proposal.md matches configured language",
|
|
10935
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11057
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "proposal.md"))
|
|
10936
11058
|
),
|
|
10937
11059
|
check(
|
|
10938
11060
|
"design.md exists",
|
|
10939
|
-
async () => await nonempty(
|
|
11061
|
+
async () => await nonempty(path17.join(changeDir, "design.md")) ? pass() : fail("")
|
|
10940
11062
|
),
|
|
10941
11063
|
check(
|
|
10942
11064
|
"design.md matches configured language",
|
|
10943
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11065
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "design.md"))
|
|
10944
11066
|
),
|
|
10945
11067
|
check(
|
|
10946
11068
|
"tasks.md exists",
|
|
10947
|
-
async () => await nonempty(
|
|
11069
|
+
async () => await nonempty(path17.join(changeDir, "tasks.md")) ? pass() : fail("")
|
|
10948
11070
|
),
|
|
10949
11071
|
check(
|
|
10950
11072
|
"tasks.md matches configured language",
|
|
10951
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11073
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "tasks.md"))
|
|
10952
11074
|
),
|
|
10953
11075
|
check("design handoff context exists", () => designHandoffContextValid(changeDir, change)),
|
|
10954
11076
|
check("design handoff markdown is traceable", () => designHandoffMarkdownTraceable(changeDir))
|
|
@@ -10999,6 +11121,7 @@ async function guardDesignChecks(output, changeDir, change) {
|
|
|
10999
11121
|
}
|
|
11000
11122
|
async function guardBuildChecks(output, changeDir, change, run) {
|
|
11001
11123
|
return runChecks(output, [
|
|
11124
|
+
check("bound branch matches workspace mode", () => boundBranchMatches(changeDir, change)),
|
|
11002
11125
|
check("isolation selected", () => isolationSelected(changeDir, change)),
|
|
11003
11126
|
check("build_mode selected", () => buildModeSelected(changeDir, change)),
|
|
11004
11127
|
check("build_mode allowed for workflow", () => buildModeAllowedForWorkflow(changeDir)),
|
|
@@ -11009,11 +11132,11 @@ async function guardBuildChecks(output, changeDir, change, run) {
|
|
|
11009
11132
|
check("Superpowers plan all tasks checked", () => planTasksAllDone(changeDir)),
|
|
11010
11133
|
check(
|
|
11011
11134
|
"proposal.md exists",
|
|
11012
|
-
async () => await nonempty(
|
|
11135
|
+
async () => await nonempty(path17.join(changeDir, "proposal.md")) ? pass() : fail("")
|
|
11013
11136
|
),
|
|
11014
11137
|
check(
|
|
11015
11138
|
"proposal.md matches configured language",
|
|
11016
|
-
() => documentLanguageMatchesConfigured(changeDir,
|
|
11139
|
+
() => documentLanguageMatchesConfigured(changeDir, path17.join(changeDir, "proposal.md"))
|
|
11017
11140
|
),
|
|
11018
11141
|
check("Superpowers plan matches configured language", async () => {
|
|
11019
11142
|
const plan = await readField(changeDir, "plan");
|
|
@@ -11030,6 +11153,7 @@ async function guardBuildChecks(output, changeDir, change, run) {
|
|
|
11030
11153
|
}
|
|
11031
11154
|
async function guardVerifyChecks(output, changeDir, change, run) {
|
|
11032
11155
|
return runChecks(output, [
|
|
11156
|
+
check("bound branch matches workspace mode", () => boundBranchMatches(changeDir, change)),
|
|
11033
11157
|
check("tasks.md all tasks checked", () => tasksAllDone(changeDir)),
|
|
11034
11158
|
// Verification command runs after tasks check — no point running tests
|
|
11035
11159
|
// if tasks.md is incomplete.
|
|
@@ -11048,16 +11172,17 @@ async function guardVerifyChecks(output, changeDir, change, run) {
|
|
|
11048
11172
|
})
|
|
11049
11173
|
]);
|
|
11050
11174
|
}
|
|
11051
|
-
async function guardArchiveChecks(output, changeDir) {
|
|
11175
|
+
async function guardArchiveChecks(output, changeDir, change) {
|
|
11052
11176
|
return runChecks(output, [
|
|
11177
|
+
check("bound branch matches workspace mode", () => boundBranchMatches(changeDir, change)),
|
|
11053
11178
|
check("archived is true", async () => await archivedIsTrue(changeDir) ? pass() : fail("")),
|
|
11054
11179
|
check(
|
|
11055
11180
|
"proposal.md exists",
|
|
11056
|
-
async () => await nonempty(
|
|
11181
|
+
async () => await nonempty(path17.join(changeDir, "proposal.md")) ? pass() : fail("")
|
|
11057
11182
|
),
|
|
11058
11183
|
check(
|
|
11059
11184
|
"design.md exists",
|
|
11060
|
-
async () => await nonempty(
|
|
11185
|
+
async () => await nonempty(path17.join(changeDir, "design.md")) ? pass() : fail("")
|
|
11061
11186
|
),
|
|
11062
11187
|
check("tasks.md all tasks checked", () => tasksAllDone(changeDir)),
|
|
11063
11188
|
check(
|
|
@@ -11066,9 +11191,10 @@ async function guardArchiveChecks(output, changeDir) {
|
|
|
11066
11191
|
)
|
|
11067
11192
|
]);
|
|
11068
11193
|
}
|
|
11069
|
-
async function applyStateUpdate(output, change, changeDir, phase
|
|
11194
|
+
async function applyStateUpdate(output, change, changeDir, phase) {
|
|
11070
11195
|
const event = CLASSIC_GUARD_TRANSITION_EVENT[phase];
|
|
11071
11196
|
if (!event) return;
|
|
11197
|
+
const context = await ensureClassicRuntimeRun(changeDir);
|
|
11072
11198
|
const result5 = applyClassicTransition(context.classic, event);
|
|
11073
11199
|
await transitionClassicRuntimeRun(changeDir, result5.classic, context.run, {
|
|
11074
11200
|
event,
|
|
@@ -11122,7 +11248,7 @@ Valid phases: open, design, build, verify, archive`
|
|
|
11122
11248
|
blocked2 = await guardBuildChecks(output, changeDir, change, runContext.run);
|
|
11123
11249
|
else if (phase === "verify")
|
|
11124
11250
|
blocked2 = await guardVerifyChecks(output, changeDir, change, runContext.run);
|
|
11125
|
-
else blocked2 = await guardArchiveChecks(output, changeDir);
|
|
11251
|
+
else blocked2 = await guardArchiveChecks(output, changeDir, change);
|
|
11126
11252
|
if (blocked2) {
|
|
11127
11253
|
output.stderr.push("");
|
|
11128
11254
|
output.stderr.push(red2("BLOCKED — fix failing checks before proceeding to next phase"));
|
|
@@ -11131,7 +11257,7 @@ Valid phases: open, design, build, verify, archive`
|
|
|
11131
11257
|
output.stderr.push("");
|
|
11132
11258
|
output.stderr.push(green2("ALL CHECKS PASSED — ready for next phase"));
|
|
11133
11259
|
if (flag === "--apply") {
|
|
11134
|
-
await applyStateUpdate(output, change, changeDir, phase
|
|
11260
|
+
await applyStateUpdate(output, change, changeDir, phase);
|
|
11135
11261
|
}
|
|
11136
11262
|
return output.toResult(0);
|
|
11137
11263
|
} catch (error) {
|
|
@@ -11144,10 +11270,10 @@ Valid phases: open, design, build, verify, archive`
|
|
|
11144
11270
|
};
|
|
11145
11271
|
|
|
11146
11272
|
// domains/comet-classic/classic-handoff.ts
|
|
11147
|
-
var
|
|
11273
|
+
var import_yaml7 = __toESM(require_dist(), 1);
|
|
11148
11274
|
import { createHash as createHash5 } from "crypto";
|
|
11149
|
-
import { promises as
|
|
11150
|
-
import
|
|
11275
|
+
import { promises as fs17, readFileSync as readFileSync2 } from "fs";
|
|
11276
|
+
import path18 from "path";
|
|
11151
11277
|
var GREEN4 = "\x1B[32m";
|
|
11152
11278
|
var RED4 = "\x1B[31m";
|
|
11153
11279
|
var YELLOW4 = "\x1B[33m";
|
|
@@ -11181,7 +11307,7 @@ var HandoffOutput = class {
|
|
|
11181
11307
|
};
|
|
11182
11308
|
async function exists5(file) {
|
|
11183
11309
|
try {
|
|
11184
|
-
await
|
|
11310
|
+
await fs17.access(file);
|
|
11185
11311
|
return true;
|
|
11186
11312
|
} catch (error) {
|
|
11187
11313
|
if (error.code === "ENOENT") return false;
|
|
@@ -11190,7 +11316,7 @@ async function exists5(file) {
|
|
|
11190
11316
|
}
|
|
11191
11317
|
async function nonempty2(file) {
|
|
11192
11318
|
try {
|
|
11193
|
-
return (await
|
|
11319
|
+
return (await fs17.stat(file)).size > 0;
|
|
11194
11320
|
} catch (error) {
|
|
11195
11321
|
if (error.code === "ENOENT") return false;
|
|
11196
11322
|
throw error;
|
|
@@ -11219,7 +11345,7 @@ async function handoffSourceFiles2(changeDir) {
|
|
|
11219
11345
|
const files = [`${changeDir}/proposal.md`, `${changeDir}/design.md`, `${changeDir}/tasks.md`];
|
|
11220
11346
|
const specs = `${changeDir}/specs`;
|
|
11221
11347
|
if (await exists5(specs)) {
|
|
11222
|
-
for (const entry2 of (await
|
|
11348
|
+
for (const entry2 of (await fs17.readdir(specs)).sort()) {
|
|
11223
11349
|
const spec = `${specs}/${entry2}/spec.md`;
|
|
11224
11350
|
if (await exists5(spec)) files.push(spec);
|
|
11225
11351
|
}
|
|
@@ -11267,7 +11393,7 @@ async function writeMarkdownContext(changeDir, change, mode, contextHash, output
|
|
|
11267
11393
|
];
|
|
11268
11394
|
for (const file of await handoffSourceFiles2(changeDir)) {
|
|
11269
11395
|
if (!await exists5(file)) continue;
|
|
11270
|
-
const content = await
|
|
11396
|
+
const content = await fs17.readFile(file, "utf8");
|
|
11271
11397
|
const total = lineCount(content);
|
|
11272
11398
|
lines.push(
|
|
11273
11399
|
`## ${file}`,
|
|
@@ -11292,7 +11418,7 @@ async function writeMarkdownContext(changeDir, change, mode, contextHash, output
|
|
|
11292
11418
|
}
|
|
11293
11419
|
lines.push("");
|
|
11294
11420
|
}
|
|
11295
|
-
await
|
|
11421
|
+
await fs17.writeFile(output, lines.join("\n"));
|
|
11296
11422
|
}
|
|
11297
11423
|
async function writeJsonContext(changeDir, change, mode, contextHash, output) {
|
|
11298
11424
|
const entries = [];
|
|
@@ -11315,7 +11441,7 @@ async function writeJsonContext(changeDir, change, mode, contextHash, output) {
|
|
|
11315
11441
|
"}",
|
|
11316
11442
|
""
|
|
11317
11443
|
].join("\n");
|
|
11318
|
-
await
|
|
11444
|
+
await fs17.writeFile(output, document);
|
|
11319
11445
|
}
|
|
11320
11446
|
async function writeSpecProjectionForFile(file, content) {
|
|
11321
11447
|
return [
|
|
@@ -11355,11 +11481,11 @@ async function writeSpecMarkdownContext(changeDir, change, contextHash, output)
|
|
|
11355
11481
|
const specs = `${changeDir}/specs`;
|
|
11356
11482
|
let projected = false;
|
|
11357
11483
|
if (await exists5(specs)) {
|
|
11358
|
-
for (const entry2 of (await
|
|
11484
|
+
for (const entry2 of (await fs17.readdir(specs)).sort()) {
|
|
11359
11485
|
const spec = `${specs}/${entry2}/spec.md`;
|
|
11360
11486
|
if (!await exists5(spec)) continue;
|
|
11361
11487
|
projected = true;
|
|
11362
|
-
lines.push(...await writeSpecProjectionForFile(spec, await
|
|
11488
|
+
lines.push(...await writeSpecProjectionForFile(spec, await fs17.readFile(spec, "utf8")));
|
|
11363
11489
|
}
|
|
11364
11490
|
}
|
|
11365
11491
|
if (!projected) {
|
|
@@ -11368,7 +11494,7 @@ async function writeSpecMarkdownContext(changeDir, change, contextHash, output)
|
|
|
11368
11494
|
lines.push(
|
|
11369
11495
|
"Full source files remain canonical. If a required heading or scenario is missing here, regenerate the handoff or read the source spec directly. Supporting files (proposal, design, tasks) are referenced by hash only."
|
|
11370
11496
|
);
|
|
11371
|
-
await
|
|
11497
|
+
await fs17.writeFile(output, lines.join("\n"));
|
|
11372
11498
|
}
|
|
11373
11499
|
async function writeSpecJsonContext(changeDir, change, contextHash, output) {
|
|
11374
11500
|
const entries = [];
|
|
@@ -11377,7 +11503,7 @@ async function writeSpecJsonContext(changeDir, change, contextHash, output) {
|
|
|
11377
11503
|
const role = /\/specs\/[^/]+\/spec\.md$/u.test(file) ? "spec" : "supporting";
|
|
11378
11504
|
entries.push({ path: file, sha256: hashFile2(file), role });
|
|
11379
11505
|
}
|
|
11380
|
-
await
|
|
11506
|
+
await fs17.writeFile(
|
|
11381
11507
|
output,
|
|
11382
11508
|
`${JSON.stringify(
|
|
11383
11509
|
{
|
|
@@ -11396,8 +11522,8 @@ async function writeSpecJsonContext(changeDir, change, contextHash, output) {
|
|
|
11396
11522
|
);
|
|
11397
11523
|
}
|
|
11398
11524
|
async function readField2(changeDir, field2) {
|
|
11399
|
-
const file =
|
|
11400
|
-
const document = (0,
|
|
11525
|
+
const file = path18.join(changeDir, ".comet.yaml");
|
|
11526
|
+
const document = (0, import_yaml7.parseDocument)(await fs17.readFile(file, "utf8"), { uniqueKeys: false });
|
|
11401
11527
|
if (document.errors.length > 0) {
|
|
11402
11528
|
throw new HandoffFailure(`ERROR: Invalid .comet.yaml: ${document.errors[0].message}`);
|
|
11403
11529
|
}
|
|
@@ -11431,7 +11557,7 @@ async function completedHandoffIsCurrent(changeDir, run, contextHash, contextJso
|
|
|
11431
11557
|
readCheckpoint(changeDir, run.checkpointRef)
|
|
11432
11558
|
]);
|
|
11433
11559
|
if (!await exists5(contextJson) || !await exists5(contextMd)) return false;
|
|
11434
|
-
if (context !== await
|
|
11560
|
+
if (context !== await fs17.readFile(contextMd, "utf8")) return false;
|
|
11435
11561
|
if (artifacts.handoff_context !== contextJson || artifacts.handoff_markdown !== contextMd) {
|
|
11436
11562
|
return false;
|
|
11437
11563
|
}
|
|
@@ -11554,7 +11680,7 @@ var classicHandoffCommand = async (args) => {
|
|
|
11554
11680
|
run: pendingRun,
|
|
11555
11681
|
unknownKeys: (await readClassicState(changeDir)).unknownKeys
|
|
11556
11682
|
});
|
|
11557
|
-
await
|
|
11683
|
+
await fs17.mkdir(handoffDir, { recursive: true });
|
|
11558
11684
|
if (handoffMode === "beta") {
|
|
11559
11685
|
await writeSpecMarkdownContext(changeDir, change, contextHash, contextMd);
|
|
11560
11686
|
await writeSpecJsonContext(changeDir, change, contextHash, contextJson);
|
|
@@ -11562,7 +11688,7 @@ var classicHandoffCommand = async (args) => {
|
|
|
11562
11688
|
await writeMarkdownContext(changeDir, change, handoffMode, contextHash, contextMd);
|
|
11563
11689
|
await writeJsonContext(changeDir, change, handoffMode, contextHash, contextJson);
|
|
11564
11690
|
}
|
|
11565
|
-
const context = await
|
|
11691
|
+
const context = await fs17.readFile(contextMd, "utf8");
|
|
11566
11692
|
await writeContext(changeDir, pendingRun.contextRef, context);
|
|
11567
11693
|
const artifacts = {
|
|
11568
11694
|
...await readArtifacts(changeDir, pendingRun.artifactsRef),
|
|
@@ -11619,37 +11745,24 @@ var classicHandoffCommand = async (args) => {
|
|
|
11619
11745
|
};
|
|
11620
11746
|
|
|
11621
11747
|
// domains/comet-classic/classic-hook-guard.ts
|
|
11622
|
-
import { existsSync as existsSync2, promises as
|
|
11623
|
-
import
|
|
11748
|
+
import { existsSync as existsSync2, promises as fs19, readFileSync as readFileSync3 } from "fs";
|
|
11749
|
+
import path20 from "path";
|
|
11624
11750
|
|
|
11625
11751
|
// domains/comet-classic/classic-current-change.ts
|
|
11626
|
-
import {
|
|
11627
|
-
import {
|
|
11628
|
-
import
|
|
11629
|
-
import path18 from "path";
|
|
11752
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
11753
|
+
import { promises as fs18 } from "fs";
|
|
11754
|
+
import path19 from "path";
|
|
11630
11755
|
function currentChangeFile(projectRoot2) {
|
|
11631
|
-
return
|
|
11632
|
-
}
|
|
11633
|
-
function currentBranch(projectRoot2) {
|
|
11634
|
-
try {
|
|
11635
|
-
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
11636
|
-
cwd: projectRoot2,
|
|
11637
|
-
encoding: "utf8",
|
|
11638
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
11639
|
-
}).trim();
|
|
11640
|
-
return branch && branch !== "HEAD" ? branch : null;
|
|
11641
|
-
} catch {
|
|
11642
|
-
return null;
|
|
11643
|
-
}
|
|
11756
|
+
return path19.join(projectRoot2, ".comet", "current-change.json");
|
|
11644
11757
|
}
|
|
11645
11758
|
function changeDirectory(projectRoot2, changeName) {
|
|
11646
|
-
return
|
|
11759
|
+
return path19.join(projectRoot2, "openspec", "changes", changeName);
|
|
11647
11760
|
}
|
|
11648
11761
|
async function validateActiveChange(projectRoot2, changeName) {
|
|
11649
11762
|
assertOpenSpecChangeName(changeName);
|
|
11650
11763
|
const changeDir = changeDirectory(projectRoot2, changeName);
|
|
11651
11764
|
try {
|
|
11652
|
-
await
|
|
11765
|
+
await fs18.access(path19.join(changeDir, ".comet.yaml"));
|
|
11653
11766
|
} catch (error) {
|
|
11654
11767
|
if (error.code === "ENOENT") {
|
|
11655
11768
|
throw new Error(
|
|
@@ -11690,30 +11803,40 @@ function parseSelection(source) {
|
|
|
11690
11803
|
throw new Error("current change selection change must be a string");
|
|
11691
11804
|
}
|
|
11692
11805
|
assertOpenSpecChangeName(record.change);
|
|
11693
|
-
if (record.branch !== null && typeof record.branch !== "string") {
|
|
11806
|
+
if (record.branch !== void 0 && record.branch !== null && typeof record.branch !== "string") {
|
|
11694
11807
|
throw new Error("current change selection branch must be a string or null");
|
|
11695
11808
|
}
|
|
11696
11809
|
return {
|
|
11697
11810
|
version: 1,
|
|
11698
11811
|
change: record.change,
|
|
11699
|
-
branch: record.branch
|
|
11812
|
+
branch: record.branch ?? null
|
|
11700
11813
|
};
|
|
11701
11814
|
}
|
|
11702
11815
|
async function selectCurrentChange(projectRoot2, changeName) {
|
|
11703
11816
|
await validateActiveChange(projectRoot2, changeName);
|
|
11817
|
+
const outcome = await resolveBranchBinding(changeDirectory(projectRoot2, changeName), {
|
|
11818
|
+
heal: true,
|
|
11819
|
+
cwd: projectRoot2
|
|
11820
|
+
});
|
|
11821
|
+
if (outcome.status === "drift") {
|
|
11822
|
+
throw new Error(driftStaleReason(changeName, outcome.boundBranch, outcome.currentBranch));
|
|
11823
|
+
}
|
|
11824
|
+
if (outcome.status === "unbound-detached") {
|
|
11825
|
+
throw new Error(unboundDetachedMessage(changeName));
|
|
11826
|
+
}
|
|
11704
11827
|
const selection = {
|
|
11705
11828
|
version: 1,
|
|
11706
11829
|
change: changeName,
|
|
11707
|
-
branch: currentBranch
|
|
11830
|
+
branch: outcome.currentBranch
|
|
11708
11831
|
};
|
|
11709
11832
|
const file = currentChangeFile(projectRoot2);
|
|
11710
|
-
const temporary = `${file}.${
|
|
11711
|
-
await
|
|
11833
|
+
const temporary = `${file}.${randomUUID7()}.tmp`;
|
|
11834
|
+
await fs18.mkdir(path19.dirname(file), { recursive: true });
|
|
11712
11835
|
try {
|
|
11713
|
-
await
|
|
11714
|
-
await
|
|
11836
|
+
await fs18.writeFile(temporary, JSON.stringify(selection, null, 2) + "\n", "utf8");
|
|
11837
|
+
await fs18.rename(temporary, file);
|
|
11715
11838
|
} catch (error) {
|
|
11716
|
-
await
|
|
11839
|
+
await fs18.rm(temporary, { force: true });
|
|
11717
11840
|
throw error;
|
|
11718
11841
|
}
|
|
11719
11842
|
return selection;
|
|
@@ -11721,7 +11844,7 @@ async function selectCurrentChange(projectRoot2, changeName) {
|
|
|
11721
11844
|
async function resolveCurrentChange(projectRoot2) {
|
|
11722
11845
|
let source;
|
|
11723
11846
|
try {
|
|
11724
|
-
source = await
|
|
11847
|
+
source = await fs18.readFile(currentChangeFile(projectRoot2), "utf8");
|
|
11725
11848
|
} catch (error) {
|
|
11726
11849
|
if (error.code === "ENOENT") return { status: "missing" };
|
|
11727
11850
|
return {
|
|
@@ -11739,17 +11862,32 @@ async function resolveCurrentChange(projectRoot2) {
|
|
|
11739
11862
|
reason: error instanceof Error ? error.message : String(error)
|
|
11740
11863
|
};
|
|
11741
11864
|
}
|
|
11742
|
-
const
|
|
11743
|
-
|
|
11865
|
+
const outcome = await resolveBranchBinding(changeDirectory(projectRoot2, selection.change), {
|
|
11866
|
+
heal: false,
|
|
11867
|
+
cwd: projectRoot2
|
|
11868
|
+
});
|
|
11869
|
+
if (outcome.status === "drift") {
|
|
11870
|
+
return {
|
|
11871
|
+
status: "stale",
|
|
11872
|
+
reason: driftStaleReason(selection.change, outcome.boundBranch, outcome.currentBranch)
|
|
11873
|
+
};
|
|
11874
|
+
}
|
|
11875
|
+
if (outcome.status === "unbound-detached") {
|
|
11876
|
+
return { status: "stale", reason: unboundDetachedMessage(selection.change) };
|
|
11877
|
+
}
|
|
11878
|
+
if (outcome.status === "ok") {
|
|
11879
|
+
return { status: "selected", selection };
|
|
11880
|
+
}
|
|
11881
|
+
if (selection.branch !== null && outcome.currentBranch !== selection.branch) {
|
|
11744
11882
|
return {
|
|
11745
11883
|
status: "stale",
|
|
11746
|
-
reason: `current change '${selection.change}' was selected on branch '${selection.branch}', current branch is '${
|
|
11884
|
+
reason: `current change '${selection.change}' was selected on branch '${selection.branch}', current branch is '${outcome.currentBranch ?? "detached HEAD"}'`
|
|
11747
11885
|
};
|
|
11748
11886
|
}
|
|
11749
11887
|
return { status: "selected", selection };
|
|
11750
11888
|
}
|
|
11751
11889
|
async function clearCurrentChange(projectRoot2) {
|
|
11752
|
-
await
|
|
11890
|
+
await fs18.rm(currentChangeFile(projectRoot2), { force: true });
|
|
11753
11891
|
}
|
|
11754
11892
|
|
|
11755
11893
|
// domains/comet-classic/classic-hook-guard.ts
|
|
@@ -11781,52 +11919,52 @@ function comparisonKey(value) {
|
|
|
11781
11919
|
function parseProjectRoot(args) {
|
|
11782
11920
|
const index = args.indexOf("--project-root");
|
|
11783
11921
|
const value = index >= 0 ? args[index + 1] : void 0;
|
|
11784
|
-
return
|
|
11922
|
+
return path20.resolve(value && !value.startsWith("--") ? value : process.cwd());
|
|
11785
11923
|
}
|
|
11786
11924
|
function relativeToProjectRoot(target, projectRoot2) {
|
|
11787
|
-
const relative = normalized(
|
|
11925
|
+
const relative = normalized(path20.relative(projectRoot2, target));
|
|
11788
11926
|
if (relative === "") return "";
|
|
11789
|
-
if (relative.startsWith("../") || relative === ".." ||
|
|
11927
|
+
if (relative.startsWith("../") || relative === ".." || path20.isAbsolute(relative)) return null;
|
|
11790
11928
|
return relative;
|
|
11791
11929
|
}
|
|
11792
11930
|
async function physicalPathForPossiblyMissingTarget(target) {
|
|
11793
|
-
const resolved =
|
|
11794
|
-
const root =
|
|
11931
|
+
const resolved = path20.resolve(target);
|
|
11932
|
+
const root = path20.parse(resolved).root;
|
|
11795
11933
|
const missingSegments = [];
|
|
11796
11934
|
let cursor = resolved;
|
|
11797
11935
|
while (cursor && cursor !== root) {
|
|
11798
11936
|
try {
|
|
11799
|
-
const physicalBase = await
|
|
11800
|
-
return
|
|
11937
|
+
const physicalBase = await fs19.realpath(cursor);
|
|
11938
|
+
return path20.join(physicalBase, ...missingSegments.reverse());
|
|
11801
11939
|
} catch (error) {
|
|
11802
11940
|
const code = error.code;
|
|
11803
11941
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
11804
|
-
missingSegments.push(
|
|
11805
|
-
cursor =
|
|
11942
|
+
missingSegments.push(path20.basename(cursor));
|
|
11943
|
+
cursor = path20.dirname(cursor);
|
|
11806
11944
|
}
|
|
11807
11945
|
}
|
|
11808
11946
|
try {
|
|
11809
|
-
const physicalRoot = await
|
|
11810
|
-
return
|
|
11947
|
+
const physicalRoot = await fs19.realpath(root);
|
|
11948
|
+
return path20.join(physicalRoot, ...missingSegments.reverse());
|
|
11811
11949
|
} catch {
|
|
11812
11950
|
return null;
|
|
11813
11951
|
}
|
|
11814
11952
|
}
|
|
11815
11953
|
async function projectRelative(target, projectRoot2) {
|
|
11816
|
-
const rawCandidate =
|
|
11954
|
+
const rawCandidate = path20.isAbsolute(target) ? target : path20.resolve(process.cwd(), target);
|
|
11817
11955
|
let candidate = normalized(rawCandidate);
|
|
11818
11956
|
const rootRelative = relativeToProjectRoot(rawCandidate, projectRoot2);
|
|
11819
11957
|
if (rootRelative !== null) return rootRelative;
|
|
11820
11958
|
try {
|
|
11821
11959
|
const physicalCandidate = await physicalPathForPossiblyMissingTarget(rawCandidate);
|
|
11822
|
-
const physicalRoot = await
|
|
11960
|
+
const physicalRoot = await fs19.realpath(projectRoot2);
|
|
11823
11961
|
if (physicalCandidate) {
|
|
11824
11962
|
const physicalRootRelative = relativeToProjectRoot(physicalCandidate, physicalRoot);
|
|
11825
11963
|
if (physicalRootRelative !== null) return physicalRootRelative;
|
|
11826
11964
|
candidate = normalized(physicalCandidate);
|
|
11827
11965
|
}
|
|
11828
11966
|
} catch {
|
|
11829
|
-
if (!
|
|
11967
|
+
if (!path20.isAbsolute(target)) return normalized(target).replace(/^\.\//u, "");
|
|
11830
11968
|
}
|
|
11831
11969
|
return candidate.replace(/^\.\//u, "");
|
|
11832
11970
|
}
|
|
@@ -11856,15 +11994,15 @@ async function loadGoverningChange(changeDir) {
|
|
|
11856
11994
|
}
|
|
11857
11995
|
}
|
|
11858
11996
|
async function activeChanges(projectRoot2) {
|
|
11859
|
-
const changesDir =
|
|
11997
|
+
const changesDir = path20.join(projectRoot2, "openspec", "changes");
|
|
11860
11998
|
const governingChanges = [];
|
|
11861
11999
|
if (!existsSync2(changesDir)) return governingChanges;
|
|
11862
|
-
for (const entry2 of (await
|
|
12000
|
+
for (const entry2 of (await fs19.readdir(changesDir, { withFileTypes: true })).sort(
|
|
11863
12001
|
(left, right) => left.name.localeCompare(right.name)
|
|
11864
12002
|
)) {
|
|
11865
12003
|
if (!entry2.isDirectory() || entry2.name === "archive") continue;
|
|
11866
|
-
const changeDir =
|
|
11867
|
-
if (!existsSync2(
|
|
12004
|
+
const changeDir = path20.join(changesDir, entry2.name);
|
|
12005
|
+
if (!existsSync2(path20.join(changeDir, ".comet.yaml"))) continue;
|
|
11868
12006
|
const governing = await loadGoverningChange(changeDir);
|
|
11869
12007
|
if (!governing || governing.archived) continue;
|
|
11870
12008
|
governingChanges.push(governing);
|
|
@@ -11912,7 +12050,7 @@ function allowsSuperpowersArtifacts(governing) {
|
|
|
11912
12050
|
return governing.phase === "design" || governing.phase === "build" || governing.phase === "verify";
|
|
11913
12051
|
}
|
|
11914
12052
|
function governingChangeName(governing) {
|
|
11915
|
-
return governing.changeDir ?
|
|
12053
|
+
return governing.changeDir ? path20.basename(governing.changeDir) : null;
|
|
11916
12054
|
}
|
|
11917
12055
|
var SUPERPOWERS_ARTIFACT_SUFFIXES = /* @__PURE__ */ new Set([
|
|
11918
12056
|
"design",
|
|
@@ -11978,7 +12116,30 @@ async function repoSourceGoverningChange(projectRoot2, relativePath2) {
|
|
|
11978
12116
|
)
|
|
11979
12117
|
};
|
|
11980
12118
|
}
|
|
11981
|
-
if (active.length === 1)
|
|
12119
|
+
if (active.length === 1) {
|
|
12120
|
+
const sole = active[0];
|
|
12121
|
+
if (sole.changeDir !== null) {
|
|
12122
|
+
const outcome = await resolveBranchBinding(sole.changeDir, {
|
|
12123
|
+
heal: false,
|
|
12124
|
+
cwd: projectRoot2
|
|
12125
|
+
});
|
|
12126
|
+
const name = governingChangeName(sole) ?? "unknown";
|
|
12127
|
+
if (outcome.status === "drift") {
|
|
12128
|
+
return {
|
|
12129
|
+
blockedResult: blockedStaleSelection(
|
|
12130
|
+
relativePath2,
|
|
12131
|
+
driftStaleReason(name, outcome.boundBranch, outcome.currentBranch)
|
|
12132
|
+
)
|
|
12133
|
+
};
|
|
12134
|
+
}
|
|
12135
|
+
if (outcome.status === "unbound-detached") {
|
|
12136
|
+
return {
|
|
12137
|
+
blockedResult: blockedStaleSelection(relativePath2, unboundDetachedMessage(name))
|
|
12138
|
+
};
|
|
12139
|
+
}
|
|
12140
|
+
}
|
|
12141
|
+
return sole;
|
|
12142
|
+
}
|
|
11982
12143
|
return {
|
|
11983
12144
|
blockedResult: blockedMultipleChanges(
|
|
11984
12145
|
relativePath2,
|
|
@@ -11992,8 +12153,8 @@ async function governingChange(relativePath2, projectRoot2) {
|
|
|
11992
12153
|
const rest = relativePath2.slice(prefix.length);
|
|
11993
12154
|
const [name] = rest.split("/");
|
|
11994
12155
|
if (name && name !== "archive") {
|
|
11995
|
-
const changeDir =
|
|
11996
|
-
const stateFile2 =
|
|
12156
|
+
const changeDir = path20.join(projectRoot2, "openspec", "changes", name);
|
|
12157
|
+
const stateFile2 = path20.join(changeDir, ".comet.yaml");
|
|
11997
12158
|
if (existsSync2(stateFile2)) {
|
|
11998
12159
|
const governing = await loadGoverningChange(changeDir);
|
|
11999
12160
|
if (governing) return governing;
|
|
@@ -12601,8 +12762,8 @@ var classicIntentCommand = async (args, _options) => {
|
|
|
12601
12762
|
};
|
|
12602
12763
|
|
|
12603
12764
|
// domains/comet-classic/classic-resume-probe.ts
|
|
12604
|
-
import
|
|
12605
|
-
import { promises as
|
|
12765
|
+
import path21 from "path";
|
|
12766
|
+
import { promises as fs20 } from "fs";
|
|
12606
12767
|
import { spawn } from "child_process";
|
|
12607
12768
|
var COMET_RESUME_PROBE_SCHEMA_VERSION = "comet.resume_probe.v1";
|
|
12608
12769
|
function isRecord2(value) {
|
|
@@ -12645,13 +12806,13 @@ function result3(action, change, confidence, reason, evidence = []) {
|
|
|
12645
12806
|
}
|
|
12646
12807
|
async function readIfExists(filePath) {
|
|
12647
12808
|
if (!await fileExists3(filePath)) return "";
|
|
12648
|
-
return
|
|
12809
|
+
return fs20.readFile(filePath, "utf8");
|
|
12649
12810
|
}
|
|
12650
12811
|
async function changeSearchText(changeDir, classic) {
|
|
12651
12812
|
const files = ["proposal.md", "design.md", "tasks.md"];
|
|
12652
12813
|
const parts = [classic.name, classic.workflow, classic.phase];
|
|
12653
12814
|
for (const file of files) {
|
|
12654
|
-
parts.push(await readIfExists(
|
|
12815
|
+
parts.push(await readIfExists(path21.join(changeDir, file)));
|
|
12655
12816
|
}
|
|
12656
12817
|
return parts.join("\n").toLowerCase();
|
|
12657
12818
|
}
|
|
@@ -12715,19 +12876,19 @@ function diagnosticFromProjection(changeDir, name, projection) {
|
|
|
12715
12876
|
};
|
|
12716
12877
|
}
|
|
12717
12878
|
async function hasOpenSpecChangeFiles(changeDir) {
|
|
12718
|
-
return await fileExists3(
|
|
12879
|
+
return await fileExists3(path21.join(changeDir, "proposal.md")) || await fileExists3(path21.join(changeDir, "design.md")) || await fileExists3(path21.join(changeDir, "tasks.md"));
|
|
12719
12880
|
}
|
|
12720
12881
|
async function discoverActiveChanges(projectRoot2) {
|
|
12721
|
-
const changesDir =
|
|
12882
|
+
const changesDir = path21.join(projectRoot2, "openspec", "changes");
|
|
12722
12883
|
if (!await fileExists3(changesDir)) return [];
|
|
12723
12884
|
const entries = await readDir(changesDir);
|
|
12724
12885
|
const changes = [];
|
|
12725
12886
|
for (const entry2 of entries) {
|
|
12726
12887
|
if (entry2 === "archive") continue;
|
|
12727
|
-
const changeDir =
|
|
12728
|
-
const stat = await
|
|
12888
|
+
const changeDir = path21.join(changesDir, entry2);
|
|
12889
|
+
const stat = await fs20.stat(changeDir).catch(() => null);
|
|
12729
12890
|
if (!stat?.isDirectory()) continue;
|
|
12730
|
-
const hasCometState = await fileExists3(
|
|
12891
|
+
const hasCometState = await fileExists3(path21.join(changeDir, ".comet.yaml"));
|
|
12731
12892
|
if (!hasCometState) {
|
|
12732
12893
|
if (!await hasOpenSpecChangeFiles(changeDir)) continue;
|
|
12733
12894
|
const missingStateChange = {
|
|
@@ -13027,11 +13188,11 @@ var classicResumeProbeCommand = async (args) => {
|
|
|
13027
13188
|
};
|
|
13028
13189
|
|
|
13029
13190
|
// domains/comet-classic/classic-state-command.ts
|
|
13030
|
-
var
|
|
13191
|
+
var import_yaml8 = __toESM(require_dist(), 1);
|
|
13031
13192
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
13032
|
-
import { randomUUID as
|
|
13033
|
-
import { existsSync as existsSync3, promises as
|
|
13034
|
-
import
|
|
13193
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
13194
|
+
import { existsSync as existsSync3, promises as fs21 } from "fs";
|
|
13195
|
+
import path22 from "path";
|
|
13035
13196
|
init_state();
|
|
13036
13197
|
var GREEN5 = "\x1B[32m";
|
|
13037
13198
|
var RED5 = "\x1B[31m";
|
|
@@ -13046,7 +13207,8 @@ var MACHINE_OWNED_FIELDS = /* @__PURE__ */ new Set([
|
|
|
13046
13207
|
"archive_confirmation",
|
|
13047
13208
|
"verify_failures",
|
|
13048
13209
|
"classic_profile",
|
|
13049
|
-
"classic_migration"
|
|
13210
|
+
"classic_migration",
|
|
13211
|
+
"bound_branch"
|
|
13050
13212
|
]);
|
|
13051
13213
|
var SETTABLE_FIELDS = new Set(
|
|
13052
13214
|
CLASSIC_WIRE_KEYS.filter((field2) => !MACHINE_OWNED_FIELDS.has(field2))
|
|
@@ -13144,7 +13306,7 @@ function validateRelativePath(value, field2) {
|
|
|
13144
13306
|
}
|
|
13145
13307
|
async function exists6(file) {
|
|
13146
13308
|
try {
|
|
13147
|
-
await
|
|
13309
|
+
await fs21.access(file);
|
|
13148
13310
|
return true;
|
|
13149
13311
|
} catch (error) {
|
|
13150
13312
|
if (error.code === "ENOENT") return false;
|
|
@@ -13153,7 +13315,7 @@ async function exists6(file) {
|
|
|
13153
13315
|
}
|
|
13154
13316
|
async function nonempty3(file) {
|
|
13155
13317
|
try {
|
|
13156
|
-
return (await
|
|
13318
|
+
return (await fs21.stat(file)).size > 0;
|
|
13157
13319
|
} catch (error) {
|
|
13158
13320
|
if (error.code === "ENOENT") return false;
|
|
13159
13321
|
throw error;
|
|
@@ -13165,27 +13327,27 @@ async function changeDirectory2(name) {
|
|
|
13165
13327
|
async function readDocument2(file) {
|
|
13166
13328
|
let source;
|
|
13167
13329
|
try {
|
|
13168
|
-
source = await
|
|
13330
|
+
source = await fs21.readFile(file, "utf8");
|
|
13169
13331
|
} catch (error) {
|
|
13170
13332
|
if (error.code === "ENOENT") {
|
|
13171
13333
|
fail2(
|
|
13172
|
-
`ERROR: .comet.yaml not found at ${
|
|
13334
|
+
`ERROR: .comet.yaml not found at ${path22.relative(process.cwd(), file).replaceAll("\\", "/")}`
|
|
13173
13335
|
);
|
|
13174
13336
|
}
|
|
13175
13337
|
throw error;
|
|
13176
13338
|
}
|
|
13177
|
-
const document = (0,
|
|
13339
|
+
const document = (0, import_yaml8.parseDocument)(source, { uniqueKeys: false });
|
|
13178
13340
|
if (document.errors.length > 0) fail2(`ERROR: Invalid .comet.yaml: ${document.errors[0].message}`);
|
|
13179
13341
|
return document;
|
|
13180
13342
|
}
|
|
13181
13343
|
async function atomicWrite2(file, content) {
|
|
13182
|
-
await
|
|
13183
|
-
const temporary = `${file}.${
|
|
13344
|
+
await fs21.mkdir(path22.dirname(file), { recursive: true });
|
|
13345
|
+
const temporary = `${file}.${randomUUID8()}.tmp`;
|
|
13184
13346
|
try {
|
|
13185
|
-
await
|
|
13186
|
-
await
|
|
13347
|
+
await fs21.writeFile(temporary, content, "utf8");
|
|
13348
|
+
await fs21.rename(temporary, file);
|
|
13187
13349
|
} catch (error) {
|
|
13188
|
-
await
|
|
13350
|
+
await fs21.rm(temporary, { force: true });
|
|
13189
13351
|
throw error;
|
|
13190
13352
|
}
|
|
13191
13353
|
}
|
|
@@ -13255,6 +13417,7 @@ function sparseClassicState(record) {
|
|
|
13255
13417
|
["current", "branch", "worktree"],
|
|
13256
13418
|
null
|
|
13257
13419
|
),
|
|
13420
|
+
boundBranch: nullableRecordString(record, "bound_branch"),
|
|
13258
13421
|
verifyMode: enumRecordValue(record, "verify_mode", ["light", "full"], null),
|
|
13259
13422
|
autoTransition: nullableRecordBoolean(record, "auto_transition"),
|
|
13260
13423
|
baseRef: nullableRecordString(record, "base_ref"),
|
|
@@ -13327,7 +13490,7 @@ async function stateFile(name) {
|
|
|
13327
13490
|
const change = await changeDirectory2(name);
|
|
13328
13491
|
return {
|
|
13329
13492
|
...change,
|
|
13330
|
-
file:
|
|
13493
|
+
file: path22.join(change.directory, ".comet.yaml")
|
|
13331
13494
|
};
|
|
13332
13495
|
}
|
|
13333
13496
|
async function readField3(name, field2) {
|
|
@@ -13345,7 +13508,7 @@ async function readField3(name, field2) {
|
|
|
13345
13508
|
return scalar(value);
|
|
13346
13509
|
}
|
|
13347
13510
|
function parsedValue(field2, value) {
|
|
13348
|
-
const document = (0,
|
|
13511
|
+
const document = (0, import_yaml8.parseDocument)(`${field2}: ${value}
|
|
13349
13512
|
`);
|
|
13350
13513
|
if (document.errors.length > 0) fail2(`ERROR: Invalid value: '${value}'`);
|
|
13351
13514
|
return document.get(field2);
|
|
@@ -13380,7 +13543,35 @@ async function setField2(output, name, field2, value, options = {}) {
|
|
|
13380
13543
|
validateSetValue(field2, value);
|
|
13381
13544
|
const { file, directory } = await stateFile(name);
|
|
13382
13545
|
const document = await readDocument2(file);
|
|
13546
|
+
const previousRecord = document.toJS() ?? {};
|
|
13383
13547
|
document.set(field2, parsedValue(field2, value));
|
|
13548
|
+
if (field2 === "isolation") {
|
|
13549
|
+
if (requiresBranchBinding(value)) {
|
|
13550
|
+
const previousIsolation = typeof previousRecord.isolation === "string" ? previousRecord.isolation : null;
|
|
13551
|
+
const existing = previousRecord.bound_branch;
|
|
13552
|
+
const alreadyBound = typeof existing === "string" && existing !== "";
|
|
13553
|
+
if (!alreadyBound || previousIsolation !== value) {
|
|
13554
|
+
const currentBranch = liveGitBranch(process.cwd());
|
|
13555
|
+
const verdict = evaluateBranchBinding({
|
|
13556
|
+
isolation: value,
|
|
13557
|
+
boundBranch: null,
|
|
13558
|
+
currentBranch,
|
|
13559
|
+
gitWorkTree: currentBranch === null ? isGitWorkTree(process.cwd()) : true
|
|
13560
|
+
});
|
|
13561
|
+
if (verdict.status === "needs-heal") {
|
|
13562
|
+
document.set("bound_branch", verdict.branch);
|
|
13563
|
+
} else if (verdict.status === "unbound-detached") {
|
|
13564
|
+
fail2(
|
|
13565
|
+
`ERROR: cannot bind isolation=${value} while HEAD is detached; checkout a branch first`
|
|
13566
|
+
);
|
|
13567
|
+
} else {
|
|
13568
|
+
document.set("bound_branch", null);
|
|
13569
|
+
}
|
|
13570
|
+
}
|
|
13571
|
+
} else {
|
|
13572
|
+
document.set("bound_branch", null);
|
|
13573
|
+
}
|
|
13574
|
+
}
|
|
13384
13575
|
const run = await readRunState(directory);
|
|
13385
13576
|
const projection = parseClassicStateDocument(document.toJS(), run);
|
|
13386
13577
|
if (projection.run) {
|
|
@@ -13430,10 +13621,10 @@ async function init(output, name, workflow) {
|
|
|
13430
13621
|
validateEnum(workflow, PROFILES);
|
|
13431
13622
|
const { file, label, directory } = await stateFile(name);
|
|
13432
13623
|
if (await exists6(file)) fail2(`ERROR: .comet.yaml already exists at ${label}/.comet.yaml`);
|
|
13433
|
-
await
|
|
13624
|
+
await fs21.mkdir(directory, { recursive: true });
|
|
13434
13625
|
const preset = workflow !== "full";
|
|
13435
13626
|
const reviewMode = preset ? "off" : await reviewModeDefault();
|
|
13436
|
-
const document = new
|
|
13627
|
+
const document = new import_yaml8.Document({
|
|
13437
13628
|
workflow,
|
|
13438
13629
|
language: await projectLanguageDefault(),
|
|
13439
13630
|
phase: "open",
|
|
@@ -13443,7 +13634,7 @@ async function init(output, name, workflow) {
|
|
|
13443
13634
|
subagent_dispatch: null,
|
|
13444
13635
|
tdd_mode: preset ? "direct" : null,
|
|
13445
13636
|
review_mode: reviewMode,
|
|
13446
|
-
isolation:
|
|
13637
|
+
isolation: null,
|
|
13447
13638
|
verify_mode: preset ? "light" : null,
|
|
13448
13639
|
auto_transition: await autoTransition() === "true",
|
|
13449
13640
|
base_ref: gitOutput(["rev-parse", "--verify", "HEAD"]),
|
|
@@ -13475,10 +13666,10 @@ async function requireBuildDecisions(name) {
|
|
|
13475
13666
|
const subagentDispatch = await readField3(name, "subagent_dispatch");
|
|
13476
13667
|
const tddMode = await readField3(name, "tdd_mode");
|
|
13477
13668
|
const reviewMode = await readField3(name, "review_mode");
|
|
13478
|
-
const allowedIsolation =
|
|
13669
|
+
const allowedIsolation = ["current", "branch", "worktree"];
|
|
13479
13670
|
if (!allowedIsolation.includes(isolation)) {
|
|
13480
13671
|
fail2(
|
|
13481
|
-
`ERROR: Cannot transition '${name}': isolation must be
|
|
13672
|
+
`ERROR: Cannot transition '${name}': isolation must be current, branch, or worktree, got '${isolation || "null"}'`
|
|
13482
13673
|
);
|
|
13483
13674
|
}
|
|
13484
13675
|
if (!["subagent-driven-development", "executing-plans", "direct"].includes(buildMode)) {
|
|
@@ -13511,13 +13702,13 @@ async function requireOpenArtifacts(name) {
|
|
|
13511
13702
|
const { directory } = await stateFile(name);
|
|
13512
13703
|
const workflow = await readField3(name, "workflow");
|
|
13513
13704
|
for (const artifact of ["proposal.md", "tasks.md"]) {
|
|
13514
|
-
if (!await nonempty3(
|
|
13705
|
+
if (!await nonempty3(path22.join(directory, artifact))) {
|
|
13515
13706
|
fail2(
|
|
13516
13707
|
`ERROR: Cannot transition '${name}': ${artifact} must exist and be non-empty before leaving open`
|
|
13517
13708
|
);
|
|
13518
13709
|
}
|
|
13519
13710
|
}
|
|
13520
|
-
if (workflow === "full" && !await nonempty3(
|
|
13711
|
+
if (workflow === "full" && !await nonempty3(path22.join(directory, "design.md"))) {
|
|
13521
13712
|
fail2(
|
|
13522
13713
|
`ERROR: Cannot transition '${name}': design.md must exist and be non-empty before leaving open`
|
|
13523
13714
|
);
|
|
@@ -13525,14 +13716,14 @@ async function requireOpenArtifacts(name) {
|
|
|
13525
13716
|
}
|
|
13526
13717
|
async function requireDesignEvidence(name) {
|
|
13527
13718
|
const designDoc = await readField3(name, "design_doc");
|
|
13528
|
-
if (!designDoc || designDoc === "null" || !await nonempty3(
|
|
13719
|
+
if (!designDoc || designDoc === "null" || !await nonempty3(path22.resolve(designDoc))) {
|
|
13529
13720
|
fail2(
|
|
13530
13721
|
`ERROR: Cannot transition '${name}': design_doc must point to an existing Design Doc before leaving design`
|
|
13531
13722
|
);
|
|
13532
13723
|
}
|
|
13533
13724
|
}
|
|
13534
13725
|
async function writeSparseTransitionEffects(directory, effects) {
|
|
13535
|
-
const file =
|
|
13726
|
+
const file = path22.join(directory, ".comet.yaml");
|
|
13536
13727
|
const document = await readDocument2(file);
|
|
13537
13728
|
for (const effect of effects) {
|
|
13538
13729
|
const field2 = wireField2(effect.field);
|
|
@@ -13547,7 +13738,7 @@ async function applyTransitionEvent(output, name, event) {
|
|
|
13547
13738
|
let sparse = false;
|
|
13548
13739
|
if (!classic) {
|
|
13549
13740
|
if (projection.run) fail2("ERROR: Classic state projection is missing");
|
|
13550
|
-
const document = await readDocument2(
|
|
13741
|
+
const document = await readDocument2(path22.join(directory, ".comet.yaml"));
|
|
13551
13742
|
classic = sparseClassicState(document.toJS());
|
|
13552
13743
|
sparse = true;
|
|
13553
13744
|
}
|
|
@@ -13594,7 +13785,7 @@ async function transition(output, name, event) {
|
|
|
13594
13785
|
} else if (event === "verify-pass") {
|
|
13595
13786
|
await requirePhase(name, "verify");
|
|
13596
13787
|
const report = await readField3(name, "verification_report");
|
|
13597
|
-
if (!report || !await exists6(
|
|
13788
|
+
if (!report || !await exists6(path22.resolve(report))) {
|
|
13598
13789
|
fail2(
|
|
13599
13790
|
`ERROR: Cannot transition '${name}': verification_report must point to an existing report file`
|
|
13600
13791
|
);
|
|
@@ -13658,9 +13849,9 @@ async function next(output, name) {
|
|
|
13658
13849
|
async function taskCheckoff(output, taskFile, taskText) {
|
|
13659
13850
|
validateRelativePath(taskFile, "task file");
|
|
13660
13851
|
if (!taskText) fail2("ERROR: Task text cannot be empty");
|
|
13661
|
-
const file =
|
|
13852
|
+
const file = path22.resolve(taskFile);
|
|
13662
13853
|
if (!await exists6(file)) fail2(`ERROR: Task file not found: ${taskFile}`);
|
|
13663
|
-
const lines = (await
|
|
13854
|
+
const lines = (await fs21.readFile(file, "utf8")).split(/\r?\n/u);
|
|
13664
13855
|
const matches = lines.filter(
|
|
13665
13856
|
(line) => [`- [ ] ${taskText}`, `- [x] ${taskText}`, `- [X] ${taskText}`].includes(line)
|
|
13666
13857
|
);
|
|
@@ -13698,21 +13889,21 @@ async function check2(output, name, phase) {
|
|
|
13698
13889
|
designDoc ? `design_doc=${designDoc} (expected: empty/null)` : "design_doc is empty/null"
|
|
13699
13890
|
);
|
|
13700
13891
|
for (const artifact of ["proposal.md", "design.md", "tasks.md"]) {
|
|
13701
|
-
(await nonempty3(
|
|
13702
|
-
`${artifact} ${await nonempty3(
|
|
13892
|
+
(await nonempty3(path22.join(directory, artifact)) ? pass2 : reject)(
|
|
13893
|
+
`${artifact} ${await nonempty3(path22.join(directory, artifact)) ? "non-empty" : "missing or empty"}`
|
|
13703
13894
|
);
|
|
13704
13895
|
}
|
|
13705
13896
|
} else if (phase === "build") {
|
|
13706
13897
|
const workflow = await readField3(name, "workflow");
|
|
13707
13898
|
const designDoc = await readField3(name, "design_doc");
|
|
13708
13899
|
if (workflow === "full") {
|
|
13709
|
-
(designDoc && designDoc !== "null" && await exists6(
|
|
13900
|
+
(designDoc && designDoc !== "null" && await exists6(path22.resolve(designDoc)) ? pass2 : reject)(`design_doc=${designDoc} (expected: non-null and file exists)`);
|
|
13710
13901
|
} else {
|
|
13711
13902
|
pass2(`workflow=${workflow} (design_doc not required)`);
|
|
13712
13903
|
}
|
|
13713
13904
|
for (const artifact of ["proposal.md", "tasks.md"]) {
|
|
13714
|
-
(await nonempty3(
|
|
13715
|
-
`${artifact} ${await nonempty3(
|
|
13905
|
+
(await nonempty3(path22.join(directory, artifact)) ? pass2 : reject)(
|
|
13906
|
+
`${artifact} ${await nonempty3(path22.join(directory, artifact)) ? "non-empty" : "missing or empty"}`
|
|
13716
13907
|
);
|
|
13717
13908
|
}
|
|
13718
13909
|
} else if (phase === "verify") {
|
|
@@ -13725,6 +13916,29 @@ async function check2(output, name, phase) {
|
|
|
13725
13916
|
const archived = await readField3(name, "archived");
|
|
13726
13917
|
(archived !== "true" ? pass2 : reject)(`archived=${archived} (expected: not true)`);
|
|
13727
13918
|
}
|
|
13919
|
+
const binding = await resolveBranchBinding(directory, { heal: true, cwd: process.cwd() });
|
|
13920
|
+
if (binding.bindingRequired) {
|
|
13921
|
+
switch (binding.status) {
|
|
13922
|
+
case "drift":
|
|
13923
|
+
reject(driftBlockedMessage(name, binding.boundBranch, binding.currentBranch));
|
|
13924
|
+
break;
|
|
13925
|
+
case "unbound-detached":
|
|
13926
|
+
reject(unboundDetachedMessage(name));
|
|
13927
|
+
break;
|
|
13928
|
+
case "healed":
|
|
13929
|
+
pass2(`bound_branch lazily set to ${binding.branch}`);
|
|
13930
|
+
break;
|
|
13931
|
+
case "needs-heal":
|
|
13932
|
+
case "ok":
|
|
13933
|
+
case "not-applicable":
|
|
13934
|
+
pass2("bound_branch matches current branch");
|
|
13935
|
+
break;
|
|
13936
|
+
default: {
|
|
13937
|
+
const exhaustive = binding;
|
|
13938
|
+
throw new Error(`unhandled branch binding status: ${JSON.stringify(exhaustive)}`);
|
|
13939
|
+
}
|
|
13940
|
+
}
|
|
13941
|
+
}
|
|
13728
13942
|
output.stdout.push("");
|
|
13729
13943
|
if (blocked2) {
|
|
13730
13944
|
output.stderr.push(red4("BLOCKED — fix failing checks before proceeding"));
|
|
@@ -13734,7 +13948,7 @@ async function check2(output, name, phase) {
|
|
|
13734
13948
|
}
|
|
13735
13949
|
function fieldStatus(field2, value, file) {
|
|
13736
13950
|
if (!value || value === "null") return ` - ${field2}: PENDING`;
|
|
13737
|
-
if (file && !existsSync3(
|
|
13951
|
+
if (file && !existsSync3(path22.resolve(file))) {
|
|
13738
13952
|
return ` - ${field2}: BROKEN (path ${value} does not exist)`;
|
|
13739
13953
|
}
|
|
13740
13954
|
return ` - ${field2}: DONE (${value})`;
|
|
@@ -13743,7 +13957,7 @@ async function recoverOpen(output, directory) {
|
|
|
13743
13957
|
output.stdout.push(" Artifacts:");
|
|
13744
13958
|
let complete = 0;
|
|
13745
13959
|
for (const artifact of ["proposal.md", "design.md", "tasks.md"]) {
|
|
13746
|
-
const done = await nonempty3(
|
|
13960
|
+
const done = await nonempty3(path22.join(directory, artifact));
|
|
13747
13961
|
if (done) complete += 1;
|
|
13748
13962
|
output.stdout.push(` - ${artifact}: ${done ? "DONE" : "PENDING"}`);
|
|
13749
13963
|
}
|
|
@@ -13756,7 +13970,7 @@ async function recoverDesign(output, name, directory) {
|
|
|
13756
13970
|
output.stdout.push(" Artifacts:");
|
|
13757
13971
|
for (const artifact of ["proposal.md", "design.md", "tasks.md"]) {
|
|
13758
13972
|
output.stdout.push(
|
|
13759
|
-
` - ${artifact}: ${await nonempty3(
|
|
13973
|
+
` - ${artifact}: ${await nonempty3(path22.join(directory, artifact)) ? "DONE" : "MISSING (unexpected in design phase)"}`
|
|
13760
13974
|
);
|
|
13761
13975
|
}
|
|
13762
13976
|
const handoff = await readField3(name, "handoff_context");
|
|
@@ -13770,11 +13984,11 @@ async function recoverDesign(output, name, directory) {
|
|
|
13770
13984
|
fieldStatus("design_doc", design, design),
|
|
13771
13985
|
""
|
|
13772
13986
|
);
|
|
13773
|
-
if (design && design !== "null" && await exists6(
|
|
13987
|
+
if (design && design !== "null" && await exists6(path22.resolve(design))) {
|
|
13774
13988
|
output.stdout.push(
|
|
13775
13989
|
"Recovery action: Design Doc already created and linked. Run guard to transition to build."
|
|
13776
13990
|
);
|
|
13777
|
-
} else if (handoff && handoff !== "null" && await exists6(
|
|
13991
|
+
} else if (handoff && handoff !== "null" && await exists6(path22.resolve(handoff))) {
|
|
13778
13992
|
output.stdout.push(
|
|
13779
13993
|
"Recovery action: Handoff generated but Design Doc not yet created. Resume from brainstorming confirmation (Step 1c)."
|
|
13780
13994
|
);
|
|
@@ -13804,7 +14018,7 @@ async function recoverBuild(output, name, directory, workflow) {
|
|
|
13804
14018
|
decisions.push(fieldStatus("subagent_dispatch", subagentDispatch));
|
|
13805
14019
|
}
|
|
13806
14020
|
output.stdout.push(...decisions, "", " Plan:", fieldStatus("plan", plan, plan), "");
|
|
13807
|
-
const tasks =
|
|
14021
|
+
const tasks = path22.join(directory, "tasks.md");
|
|
13808
14022
|
if (!await exists6(tasks)) {
|
|
13809
14023
|
output.stdout.push(
|
|
13810
14024
|
" Tasks: tasks.md MISSING",
|
|
@@ -13813,14 +14027,14 @@ async function recoverBuild(output, name, directory, workflow) {
|
|
|
13813
14027
|
);
|
|
13814
14028
|
return;
|
|
13815
14029
|
}
|
|
13816
|
-
const lines = (await
|
|
14030
|
+
const lines = (await fs21.readFile(tasks, "utf8")).split(/\r?\n/u);
|
|
13817
14031
|
const total = lines.filter((line) => /^\s*- \[[ xX]\] /u.test(line)).length;
|
|
13818
14032
|
const done = lines.filter((line) => /^\s*- \[[xX]\] /u.test(line)).length;
|
|
13819
14033
|
const pending = total - done;
|
|
13820
14034
|
let planTotal = 0;
|
|
13821
14035
|
let planDone = 0;
|
|
13822
|
-
if (plan && plan !== "null" && await exists6(
|
|
13823
|
-
const planLines = (await
|
|
14036
|
+
if (plan && plan !== "null" && await exists6(path22.resolve(plan))) {
|
|
14037
|
+
const planLines = (await fs21.readFile(path22.resolve(plan), "utf8")).split(/\r?\n/u);
|
|
13824
14038
|
planTotal = planLines.filter((line) => /^\s*- \[[ xX]\] /u.test(line)).length;
|
|
13825
14039
|
planDone = planLines.filter((line) => /^\s*- \[[xX]\] /u.test(line)).length;
|
|
13826
14040
|
}
|
|
@@ -13951,19 +14165,19 @@ async function scale(output, name) {
|
|
|
13951
14165
|
validateChangeName4(name);
|
|
13952
14166
|
const { file, directory, label } = await stateFile(name);
|
|
13953
14167
|
if (!await exists6(file)) fail2(`ERROR: .comet.yaml not found at ${label}/.comet.yaml`);
|
|
13954
|
-
const tasksFile =
|
|
13955
|
-
const taskCount = await exists6(tasksFile) ? (await
|
|
13956
|
-
const specs =
|
|
14168
|
+
const tasksFile = path22.join(directory, "tasks.md");
|
|
14169
|
+
const taskCount = await exists6(tasksFile) ? (await fs21.readFile(tasksFile, "utf8")).split(/\r?\n/u).filter((line) => /^- \[/u.test(line)).length : 0;
|
|
14170
|
+
const specs = path22.join(directory, "specs");
|
|
13957
14171
|
let deltaSpecs = 0;
|
|
13958
14172
|
if (await exists6(specs)) {
|
|
13959
|
-
for (const entry2 of await
|
|
13960
|
-
if (await exists6(
|
|
14173
|
+
for (const entry2 of await fs21.readdir(specs)) {
|
|
14174
|
+
if (await exists6(path22.join(specs, entry2, "spec.md"))) deltaSpecs += 1;
|
|
13961
14175
|
}
|
|
13962
14176
|
}
|
|
13963
14177
|
const plan = await readField3(name, "plan");
|
|
13964
14178
|
let baseRef = "";
|
|
13965
|
-
if (plan && plan !== "null" && await exists6(
|
|
13966
|
-
const match = (await
|
|
14179
|
+
if (plan && plan !== "null" && await exists6(path22.resolve(plan))) {
|
|
14180
|
+
const match = (await fs21.readFile(path22.resolve(plan), "utf8")).match(/^base-ref:\s*(.+)$/mu);
|
|
13967
14181
|
baseRef = match?.[1].trim() ?? "";
|
|
13968
14182
|
}
|
|
13969
14183
|
if (!baseRef) baseRef = await readField3(name, "base_ref");
|
|
@@ -14043,15 +14257,42 @@ async function selectChange(output, name) {
|
|
|
14043
14257
|
validateChangeName4(name);
|
|
14044
14258
|
try {
|
|
14045
14259
|
const selection = await selectCurrentChange(process.cwd(), name);
|
|
14260
|
+
const boundBranch = await readField3(name, "bound_branch");
|
|
14261
|
+
const bound = boundBranch && boundBranch !== "null" ? boundBranch : null;
|
|
14046
14262
|
output.stderr.push(
|
|
14047
|
-
green4(
|
|
14048
|
-
`[SELECTED] current change: ${selection.change}${selection.branch ? ` (branch: ${selection.branch})` : ""}`
|
|
14049
|
-
)
|
|
14263
|
+
green4(`[SELECTED] current change: ${selection.change}${bound ? ` (branch: ${bound})` : ""}`)
|
|
14050
14264
|
);
|
|
14051
14265
|
} catch (error) {
|
|
14052
14266
|
fail2(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
|
|
14053
14267
|
}
|
|
14054
14268
|
}
|
|
14269
|
+
async function rebind(output, name) {
|
|
14270
|
+
validateChangeName4(name);
|
|
14271
|
+
const { directory } = await stateFile(name);
|
|
14272
|
+
const boundBranch = await readField3(name, "bound_branch");
|
|
14273
|
+
if (!boundBranch || boundBranch === "null") {
|
|
14274
|
+
fail2(
|
|
14275
|
+
`ERROR: '${name}' is not yet bound; use 'comet state set ${name} isolation <current|branch|worktree>' to establish the first binding`
|
|
14276
|
+
);
|
|
14277
|
+
}
|
|
14278
|
+
const branch = liveGitBranch(process.cwd());
|
|
14279
|
+
if (branch === null) {
|
|
14280
|
+
fail2("ERROR: cannot rebind while HEAD is detached; checkout a branch first");
|
|
14281
|
+
}
|
|
14282
|
+
const before = await readClassicState(directory);
|
|
14283
|
+
if (!before.classic) fail2("ERROR: Classic state projection is missing");
|
|
14284
|
+
await healBoundBranch(directory, branch);
|
|
14285
|
+
const after = { ...before.classic, boundBranch: branch };
|
|
14286
|
+
await appendClassicStateEvent(directory, {
|
|
14287
|
+
change: name,
|
|
14288
|
+
event: "rebind",
|
|
14289
|
+
source: "comet-state",
|
|
14290
|
+
from: before.classic,
|
|
14291
|
+
to: after,
|
|
14292
|
+
effects: [{ field: "boundBranch", from: boundBranch, to: branch }]
|
|
14293
|
+
});
|
|
14294
|
+
output.stderr.push(green4(`[REBIND] bound_branch: ${boundBranch} → ${branch}`));
|
|
14295
|
+
}
|
|
14055
14296
|
async function currentChange(output) {
|
|
14056
14297
|
const resolution = await resolveCurrentChange(process.cwd());
|
|
14057
14298
|
if (resolution.status === "selected") {
|
|
@@ -14105,6 +14346,9 @@ var classicStateCommand = async (args) => {
|
|
|
14105
14346
|
} else if (subcommand === "task-checkoff") {
|
|
14106
14347
|
required(rest, 2, "Usage: comet-state.mjs task-checkoff <file> <task-text>");
|
|
14107
14348
|
await taskCheckoff(output, rest[0], rest[1]);
|
|
14349
|
+
} else if (subcommand === "rebind") {
|
|
14350
|
+
requiredExact(rest, 1, "Usage: comet-state.mjs rebind <change-name>");
|
|
14351
|
+
await rebind(output, rest[0]);
|
|
14108
14352
|
} else if (subcommand === "select") {
|
|
14109
14353
|
requiredExact(rest, 1, "Usage: comet-state.mjs select <change-name>");
|
|
14110
14354
|
await selectChange(output, rest[0]);
|