@coopcli/specplan 5.5.7 → 2026.817.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +399 -26
- package/dist/client/assets/{abnfDiagram-VRR7QNED-DYWyHZ9F.js → abnfDiagram-VRR7QNED-BYxvjjLu.js} +1 -1
- package/dist/client/assets/architecture-TIHT7OUA-Cz2n_8dt.js +1 -0
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-C_eOLe63.js → architectureDiagram-ZJ3FMSHR-DaKJ9vFA.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-tsATc02E.js → blockDiagram-677ZJIJ3-xETyUDB6.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-DYnH3lWl.js → c4Diagram-LMCZKHZV-CJGUNAXt.js} +1 -1
- package/dist/client/assets/channel-CuBMvOiT.js +1 -0
- package/dist/client/assets/{chunk-32BRIVSS-BXANaFgi.js → chunk-32BRIVSS-6Rh4CCUQ.js} +1 -1
- package/dist/client/assets/{chunk-52WLFC77-Kb6jbJaM.js → chunk-52WLFC77-A0SkaTYM.js} +1 -1
- package/dist/client/assets/{chunk-C7G6YPKG-Dx3Quqbt.js → chunk-C7G6YPKG-BMphmVWl.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-Cvj4C7h5.js → chunk-EX3LRPZG-xUcXnDRE.js} +1 -1
- package/dist/client/assets/{chunk-FWX5IMBZ-Dwmh6LfQ.js → chunk-FWX5IMBZ-C_mfbGNa.js} +2 -2
- package/dist/client/assets/{chunk-HOUHSVGY-B9wU44cg.js → chunk-HOUHSVGY-yNNNImSs.js} +1 -1
- package/dist/client/assets/{chunk-ICXQ74PX-BSyEFY66.js → chunk-ICXQ74PX-CyZZ3kFK.js} +1 -1
- package/dist/client/assets/{chunk-K2UTITRG-CoqZrE_Y.js → chunk-K2UTITRG-CL9ravuf.js} +3 -3
- package/dist/client/assets/{chunk-MOJQB5TN-CHtb7Cmu.js → chunk-MOJQB5TN-Bio7zOcN.js} +1 -1
- package/dist/client/assets/{chunk-OGEWGWER-1nrDWRbg.js → chunk-OGEWGWER-vwCurcM9.js} +1 -1
- package/dist/client/assets/{chunk-PUDLZKDR-COVK5AOh.js → chunk-PUDLZKDR-BWhPp8B4.js} +1 -1
- package/dist/client/assets/{chunk-Q4XR5HBZ-Bte6Z4Y6.js → chunk-Q4XR5HBZ-CvZ-Pz5B.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-Y7iEjw2i.js → chunk-RYQCIY6F-CtK1BTrZ.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-CsT2riud.js → chunk-V7JOEXUC-BP5dRCwG.js} +1 -1
- package/dist/client/assets/chunk-VAUOI2AC-D7taetBr.js +1 -0
- package/dist/client/assets/{chunk-VR4S4FIN-m_oV_gzU.js → chunk-VR4S4FIN-Q-TN3CuI.js} +1 -1
- package/dist/client/assets/{chunk-WYO6CB5R-DWSMU6gp.js → chunk-WYO6CB5R-qHfws5Na.js} +1 -1
- package/dist/client/assets/chunk-XXDRQBXY-BSmtn7NR.js +1 -0
- package/dist/client/assets/{chunk-ZGVPDNZ5-CEFMjdw9.js → chunk-ZGVPDNZ5-CHDpAKTq.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-B8_i7rCq.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-B8_i7rCq.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Df934yzX.js → cose-bilkent-JH36ORCC-dsywqjX4.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-ClnIw26J.js → cynefin-VYW2F7L2-CeEsCHXm.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-CPRYiRX_.js → cynefinDiagram-TSTJHNR4-DXw0GuLE.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-C4nF9q2L.js → dagre-VKFMJZFB-D7iVblaF.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-CGFM396M.js → diagram-FQU43EPY-D8k_N9yq.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-BXvyFAv9.js → diagram-G47NLZAW-CH7ECldl.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-2R_WWGMz.js → diagram-NH7WQ7WH-eJ3LFOko.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-WUv5u0Y5.js → diagram-OA4YK3LP-BcBLwxof.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-CNb7I-i_.js → diagram-WEI45ONY-g4Ydc6Nh.js} +1 -1
- package/dist/client/assets/{dist-DK2USxRt.js → dist-D2xnLDqk.js} +4 -4
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DlMvqVKR.js → ebnfDiagram-CCIWWBDH-nijnOCKy.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-nQNZerKT.js → erDiagram-Q63AITRT-Dj7bPJ8h.js} +1 -1
- package/dist/client/assets/eventmodeling-45OFAUF4-D-h9umx1.js +1 -0
- package/dist/client/assets/flowDiagram-23GEKE2U-C4Gtyi00.js +1 -0
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-LgOQ3f8i.js → ganttDiagram-NO4QXBWP-BKwUgAKS.js} +1 -1
- package/dist/client/assets/{gitGraph-TEB2WS4Q-h8YftM84.js → gitGraph-TEB2WS4Q-C3nx5-er.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-C-2lUGWN.js → gitGraphDiagram-IHSO6WYX-xX_vEW-n.js} +1 -1
- package/dist/client/assets/image-GAAHSSAO-BS5tZem_.js +1 -0
- package/dist/client/assets/{index-Dgo32IwH.css → index-Cc2C1ER1.css} +1 -1
- package/dist/client/assets/index-CcRUbBLK.js +92 -0
- package/dist/client/assets/{info-DKCQHKI2-CPjn8MeM.js → info-DKCQHKI2-DtWLyMEA.js} +1 -1
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BWNia170.js → infoDiagram-FWYZ7A6U-uJrkO1jE.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-DzGb1nwn.js → ishikawaDiagram-FXEZZL3T-grtc_5Gw.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-DCQtd1UV.js → journeyDiagram-5HDEW3XC-D5mpwyTK.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-TU2lEHmm.js → kanban-definition-HUTT4EX6-C8FJz-rK.js} +1 -1
- package/dist/client/assets/{line-Bhnb99qa.js → line-FNoxgdds.js} +1 -1
- package/dist/client/assets/{linear-6pbkJ0-_.js → linear-CmlO4_aU.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-OvIPAtjO.js → mermaid-parser.core-CWtD7LBQ.js} +3 -3
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-CVP_9PTJ.js → mindmap-definition-LN4V7U3C-c0zWDYkC.js} +1 -1
- package/dist/client/assets/{packet-7NZHBO7P-DZU_wayH.js → packet-7NZHBO7P-CFjBM6rf.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-B5h_0epx.js → pegDiagram-2B236MQR-D5EdJTGE.js} +1 -1
- package/dist/client/assets/{pie-RZYD4A2V-D1Ivmk2g.js → pie-RZYD4A2V-DzXcKfJc.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-DFFO1w4T.js → pieDiagram-ENE6RG2P-CfJdJPbc.js} +1 -1
- package/dist/client/assets/{prod-CkjOyc2K.js → prod-DxrTzQHE.js} +5 -5
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-CV3J7KXU.js → quadrantDiagram-ABIIQ3AL-BERLxAab.js} +1 -1
- package/dist/client/assets/{radar-I7S5WNFK-8MrgTeSz.js → radar-I7S5WNFK-BTKCUdOD.js} +1 -1
- package/dist/client/assets/{railroad-3IZDKUUU-C5HUKwxx.js → railroad-3IZDKUUU-BqlJYKVR.js} +1 -1
- package/dist/client/assets/railroad-abnf-AHOZXSZD-sY1lWP7G.js +1 -0
- package/dist/client/assets/railroad-ebnf-EBAXGLYW-D9qoZ2zn.js +1 -0
- package/dist/client/assets/railroad-peg-LSFZ7HO6-ChaYlBF0.js +1 -0
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-CKh8T4lJ.js → railroadDiagram-RFXS5EU6-CjYvoaz7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-DjXuDke9.js → requirementDiagram-TGXJPOKE-CBOHTTAP.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-BejNjkdX.js → sankeyDiagram-HTMAVEWB-BhhsYxus.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BvdwpZEb.js → sequenceDiagram-DBY2YBRQ-tBrxwb5x.js} +1 -1
- package/dist/client/assets/{src-_g_WvJI1.js → src-B56W74gN.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-BnGJpSFl.js → stateDiagram-2N3HPSRC-CETKX_oh.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Q_oKpxae.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-B0KYjtx_.js → swimlanes-5IMT3BWC-Cp3oOe65.js} +1 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-BlNqO3f1.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-CeR9YvHk.js → timeline-definition-FHXFAJF6-D4yGmEXE.js} +1 -1
- package/dist/client/assets/{treeView-QDETBFTQ-7aggPVAO.js → treeView-QDETBFTQ-Bq0uOqri.js} +1 -1
- package/dist/client/assets/{treemap-6X3UGDF4-BIYXi5iS.js → treemap-6X3UGDF4-DEB6PYX-.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-CfmtNUHK.js → vennDiagram-L72KCM5P-D5QdiCle.js} +1 -1
- package/dist/client/assets/{wardley-OPB4EBWU-D_cAHc5x.js → wardley-OPB4EBWU-DySMTzU2.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-BNoQXjla.js → wardleyDiagram-EHGQE667-B2KHXQxE.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CPMslibf.js → xychartDiagram-FW5EYKEG-HxzAIqAC.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +3 -4
- package/dist/client/assets/architecture-TIHT7OUA-CPH7BDQd.js +0 -1
- package/dist/client/assets/channel-aEQHEbQr.js +0 -1
- package/dist/client/assets/chunk-VAUOI2AC-D14Dfnyc.js +0 -1
- package/dist/client/assets/chunk-XXDRQBXY-3irJBvUF.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-DvYYcX0E.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-DvYYcX0E.js +0 -1
- package/dist/client/assets/eventmodeling-45OFAUF4-BghYzyif.js +0 -1
- package/dist/client/assets/flowDiagram-23GEKE2U-Ds32-z1u.js +0 -1
- package/dist/client/assets/image-GAAHSSAO-8w7tFoBO.js +0 -1
- package/dist/client/assets/index-CX5vrCEC.js +0 -92
- package/dist/client/assets/railroad-abnf-AHOZXSZD-CmbAkvRY.js +0 -1
- package/dist/client/assets/railroad-ebnf-EBAXGLYW-B46ilc8r.js +0 -1
- package/dist/client/assets/railroad-peg-LSFZ7HO6-JRE4sO_Q.js +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-mYNG_j99.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CdcAToWt.js +0 -8
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// server/cli.ts
|
|
4
4
|
import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "node:fs";
|
|
5
5
|
import { homedir as homedir3 } from "node:os";
|
|
6
|
-
import { dirname as
|
|
6
|
+
import { dirname as dirname8, extname, join as join14, normalize as normalize2, resolve as resolve6 } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
9
9
|
import { serve } from "@hono/node-server";
|
|
@@ -121,7 +121,11 @@ function* mapMessage(message, expectStructuredOutput) {
|
|
|
121
121
|
switch (message.type) {
|
|
122
122
|
case "system": {
|
|
123
123
|
if (message.subtype === "init") {
|
|
124
|
-
yield {
|
|
124
|
+
yield {
|
|
125
|
+
type: "start",
|
|
126
|
+
sessionId: message.session_id,
|
|
127
|
+
...message.apiKeySource !== void 0 ? { apiKeySource: message.apiKeySource } : {}
|
|
128
|
+
};
|
|
125
129
|
}
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
@@ -891,6 +895,9 @@ var nodeSyncFs = {
|
|
|
891
895
|
removeFile(path) {
|
|
892
896
|
rmSync(path, { force: true });
|
|
893
897
|
},
|
|
898
|
+
removeDir(path) {
|
|
899
|
+
rmSync(path, { force: true, recursive: true });
|
|
900
|
+
},
|
|
894
901
|
exists(path) {
|
|
895
902
|
return existsSync5(path);
|
|
896
903
|
},
|
|
@@ -918,11 +925,11 @@ function readTaskProgress(changeDir, fsx = nodeSyncFs) {
|
|
|
918
925
|
return md === void 0 ? null : parseTaskProgress(md);
|
|
919
926
|
}
|
|
920
927
|
function parseLedgerStatus(md, specId) {
|
|
921
|
-
const
|
|
928
|
+
const cellsOf3 = (line) => line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
922
929
|
let statusIdx = 2;
|
|
923
930
|
for (const line of md.split(/\r?\n/)) {
|
|
924
931
|
if (!line.trim().startsWith("|")) continue;
|
|
925
|
-
const cells =
|
|
932
|
+
const cells = cellsOf3(line);
|
|
926
933
|
if (cells.every((c) => /^:?-+:?$/.test(c))) continue;
|
|
927
934
|
const lower = cells.map((c) => c.toLowerCase());
|
|
928
935
|
if (lower.includes("status") && lower.includes("change")) {
|
|
@@ -1078,10 +1085,10 @@ var PlanJournal = class {
|
|
|
1078
1085
|
}
|
|
1079
1086
|
if (ledgerMd !== void 0) {
|
|
1080
1087
|
const status = parseLedgerStatus(ledgerMd, targetId);
|
|
1081
|
-
if (status === void 0 || !/^approved\b/i.test(status)) {
|
|
1088
|
+
if (status === void 0 || !/^(archived|approved)\b/i.test(status)) {
|
|
1082
1089
|
reports.push({
|
|
1083
1090
|
targetId,
|
|
1084
|
-
expected: "SPECS.md row status
|
|
1091
|
+
expected: "SPECS.md row status archived",
|
|
1085
1092
|
actual: status === void 0 ? "no SPECS.md row" : `SPECS.md row status "${status}"`,
|
|
1086
1093
|
detail: `journal records approved: true for ${targetId} but its SPECS.md ledger row ${status === void 0 ? "is missing" : `reads "${status}"`}`
|
|
1087
1094
|
});
|
|
@@ -1701,7 +1708,7 @@ function locateArchivedDir(store, specId) {
|
|
|
1701
1708
|
const newest = names[names.length - 1];
|
|
1702
1709
|
return newest ? join7("changes", "archive", newest) : null;
|
|
1703
1710
|
}
|
|
1704
|
-
var
|
|
1711
|
+
var ARCHIVED_LEDGER_STATUS = "archived";
|
|
1705
1712
|
function cellsOf(line) {
|
|
1706
1713
|
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
1707
1714
|
}
|
|
@@ -1765,12 +1772,12 @@ function maintainLedgerRow(store, specId, archivedRelDir) {
|
|
|
1765
1772
|
continue;
|
|
1766
1773
|
}
|
|
1767
1774
|
if ((cells[0] ?? "").includes(token)) {
|
|
1768
|
-
lines[i] = replaceStatusCell(line, statusIdx,
|
|
1775
|
+
lines[i] = replaceStatusCell(line, statusIdx, ARCHIVED_LEDGER_STATUS);
|
|
1769
1776
|
store.fsx.writeAtomicFile(ledgerPath, lines.join("\n"));
|
|
1770
1777
|
return "updated";
|
|
1771
1778
|
}
|
|
1772
1779
|
}
|
|
1773
|
-
const row2 = `| ${token} (archived \`${archivedBase}\`) | ${deriveDomains(store, archivedRelDir)} | ${
|
|
1780
|
+
const row2 = `| ${token} (archived \`${archivedBase}\`) | ${deriveDomains(store, archivedRelDir)} | ${ARCHIVED_LEDGER_STATUS} | (auto-created by specplan apply) |`;
|
|
1774
1781
|
const insertAt = activeTableInsertIndex(lines);
|
|
1775
1782
|
if (insertAt !== null) {
|
|
1776
1783
|
lines.splice(insertAt, 0, row2);
|
|
@@ -1782,7 +1789,7 @@ function maintainLedgerRow(store, specId, archivedRelDir) {
|
|
|
1782
1789
|
${freshLedger(row2)}`);
|
|
1783
1790
|
return "created";
|
|
1784
1791
|
}
|
|
1785
|
-
const row = `| ${token} (archived \`${archivedBase}\`) | ${deriveDomains(store, archivedRelDir)} | ${
|
|
1792
|
+
const row = `| ${token} (archived \`${archivedBase}\`) | ${deriveDomains(store, archivedRelDir)} | ${ARCHIVED_LEDGER_STATUS} | (auto-created by specplan apply) |`;
|
|
1786
1793
|
store.fsx.writeAtomicFile(ledgerPath, freshLedger(row));
|
|
1787
1794
|
return "created";
|
|
1788
1795
|
}
|
|
@@ -2094,6 +2101,44 @@ var PlanStore = class {
|
|
|
2094
2101
|
}
|
|
2095
2102
|
return edge;
|
|
2096
2103
|
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Drop a proposal node and every edge touching it — the counterpart to
|
|
2106
|
+
* `adoptExistingChanges`, which adds a node for every change directory and
|
|
2107
|
+
* (until this existed) never removed one.
|
|
2108
|
+
*
|
|
2109
|
+
* Deleting a change without this leaves a node whose `specFile` points at a
|
|
2110
|
+
* directory that is gone: a dangling reference on the canvas, which is the
|
|
2111
|
+
* SAME failure as the stale roadmap row this change exists to fix. Observed
|
|
2112
|
+
* live — deleting a scratch change left its node behind in `specplan.yaml`.
|
|
2113
|
+
*
|
|
2114
|
+
* Returns false when no such node exists, which is not an error: a change can
|
|
2115
|
+
* be deleted before the plan ever adopted it.
|
|
2116
|
+
*/
|
|
2117
|
+
removeProposal(nodeId) {
|
|
2118
|
+
const doc = this.loadDoc();
|
|
2119
|
+
const plan = this.planOf(doc);
|
|
2120
|
+
const idx = plan.nodes.findIndex((n) => n.id === nodeId);
|
|
2121
|
+
if (idx < 0) return false;
|
|
2122
|
+
const doomed = plan.edges.map((edge, i) => ({ edge, i })).filter(({ edge }) => edge.from === nodeId || edge.to === nodeId).map(({ i }) => i).reverse();
|
|
2123
|
+
for (const i of doomed) doc.deleteIn(["edges", i]);
|
|
2124
|
+
doc.deleteIn(["nodes", idx]);
|
|
2125
|
+
this.save(doc);
|
|
2126
|
+
const next = {
|
|
2127
|
+
...plan,
|
|
2128
|
+
nodes: plan.nodes.filter((_, i) => i !== idx),
|
|
2129
|
+
edges: plan.edges.filter((e) => e.from !== nodeId && e.to !== nodeId)
|
|
2130
|
+
};
|
|
2131
|
+
for (const source of new Set(
|
|
2132
|
+
plan.edges.filter((e) => e.to === nodeId).map((e) => e.from)
|
|
2133
|
+
)) {
|
|
2134
|
+
if (next.nodes.find((n) => n.id === source)?.type !== "proposal") continue;
|
|
2135
|
+
try {
|
|
2136
|
+
this.writeSpecMeta(source, containedStories(next, source), specDependencies(next, source));
|
|
2137
|
+
} catch {
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
return true;
|
|
2141
|
+
}
|
|
2097
2142
|
/** Update one node's sibling `order` hint. Never touches edges. */
|
|
2098
2143
|
reorder(nodeId, order) {
|
|
2099
2144
|
const doc = this.loadDoc();
|
|
@@ -2570,6 +2615,265 @@ function registerPlanRoutes(app, store, workspace, opts = {}) {
|
|
|
2570
2615
|
});
|
|
2571
2616
|
}
|
|
2572
2617
|
|
|
2618
|
+
// server/app.ts
|
|
2619
|
+
import { dirname as dirname6, join as join11 } from "node:path";
|
|
2620
|
+
|
|
2621
|
+
// server/gallery.ts
|
|
2622
|
+
import { join as join10 } from "node:path";
|
|
2623
|
+
var SNIPPET_LIMIT = 180;
|
|
2624
|
+
var REQUIRED_PROPOSAL_SECTIONS = [
|
|
2625
|
+
{ title: "Why", level: 2 },
|
|
2626
|
+
{ title: "What Changes", level: 2 },
|
|
2627
|
+
{ title: "System context", level: 3 },
|
|
2628
|
+
{ title: "Containers", level: 3 },
|
|
2629
|
+
{ title: "Components", level: 3 },
|
|
2630
|
+
{ title: "Impact", level: 2 }
|
|
2631
|
+
];
|
|
2632
|
+
function titleOf(proposal, id) {
|
|
2633
|
+
const heading = /^#[ \t]+(.+)$/m.exec(proposal ?? "");
|
|
2634
|
+
const text = heading?.[1]?.trim();
|
|
2635
|
+
return text === void 0 || text === "" ? id : text;
|
|
2636
|
+
}
|
|
2637
|
+
function snippetOf(proposal, limit = SNIPPET_LIMIT) {
|
|
2638
|
+
if (!proposal) return "";
|
|
2639
|
+
const why = /^##[ \t]+Why[ \t]*$/m.exec(proposal);
|
|
2640
|
+
if (!why) return "";
|
|
2641
|
+
const after = proposal.slice(why.index + why[0].length);
|
|
2642
|
+
const untilNextHeading = after.split(/^#{1,6}[ \t]/m)[0] ?? "";
|
|
2643
|
+
const paragraph = untilNextHeading.split(/\n\s*\n/).map((p) => p.trim()).find((p) => p !== "");
|
|
2644
|
+
if (paragraph === void 0) return "";
|
|
2645
|
+
const flat = paragraph.replace(/\s+/g, " ").trim();
|
|
2646
|
+
return truncateOnWord(flat, limit);
|
|
2647
|
+
}
|
|
2648
|
+
function truncateOnWord(text, limit) {
|
|
2649
|
+
if (text.length <= limit) return text;
|
|
2650
|
+
const clipped = text.slice(0, limit);
|
|
2651
|
+
const lastSpace = clipped.lastIndexOf(" ");
|
|
2652
|
+
return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}\u2026`;
|
|
2653
|
+
}
|
|
2654
|
+
function taskCounts(tasks) {
|
|
2655
|
+
if (!tasks) return { done: 0, total: 0 };
|
|
2656
|
+
const items = tasks.match(/^[ \t]*-[ \t]+\[[ xX]\]/gm) ?? [];
|
|
2657
|
+
const done = items.filter((i) => /\[[xX]\]/.test(i)).length;
|
|
2658
|
+
return { done, total: items.length };
|
|
2659
|
+
}
|
|
2660
|
+
function proposalState(proposal) {
|
|
2661
|
+
if (!proposal || proposal.trim() === "") return "absent";
|
|
2662
|
+
let filled = 0;
|
|
2663
|
+
for (const section of REQUIRED_PROPOSAL_SECTIONS) {
|
|
2664
|
+
const hashes = "#".repeat(section.level);
|
|
2665
|
+
const heading = new RegExp(`^${hashes}[ \\t]+${section.title}[ \\t]*$`, "m").exec(proposal);
|
|
2666
|
+
if (!heading) continue;
|
|
2667
|
+
const after = proposal.slice(heading.index + heading[0].length);
|
|
2668
|
+
const body = (after.split(/^#{1,6}[ \t]/m)[0] ?? "").trim();
|
|
2669
|
+
if (body !== "") filled += 1;
|
|
2670
|
+
}
|
|
2671
|
+
if (filled === 0) return "absent";
|
|
2672
|
+
return filled === REQUIRED_PROPOSAL_SECTIONS.length ? "complete" : "partial";
|
|
2673
|
+
}
|
|
2674
|
+
function designState(design) {
|
|
2675
|
+
if (!design || design.trim() === "") return "absent";
|
|
2676
|
+
return /^##[ \t]+\S/m.test(design) ? "complete" : "absent";
|
|
2677
|
+
}
|
|
2678
|
+
var DATE_PREFIX = /^\d{4}-\d{2}-\d{2}-/;
|
|
2679
|
+
function archivedIds(deps) {
|
|
2680
|
+
const dir = join10(deps.root, "changes", "archive");
|
|
2681
|
+
if (!deps.fsx.exists(dir)) return /* @__PURE__ */ new Set();
|
|
2682
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2683
|
+
for (const entry of deps.fsx.readdir(dir)) {
|
|
2684
|
+
if (!entry.directory || entry.name.startsWith(".")) continue;
|
|
2685
|
+
ids.add(entry.name.replace(DATE_PREFIX, ""));
|
|
2686
|
+
ids.add(entry.name);
|
|
2687
|
+
}
|
|
2688
|
+
return ids;
|
|
2689
|
+
}
|
|
2690
|
+
function listChanges(deps) {
|
|
2691
|
+
const changesDir = join10(deps.root, "changes");
|
|
2692
|
+
if (!deps.fsx.exists(changesDir)) return [];
|
|
2693
|
+
const cards = [];
|
|
2694
|
+
for (const entry of deps.fsx.readdir(changesDir).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2695
|
+
if (!entry.directory || entry.name === "archive" || entry.name.startsWith(".")) continue;
|
|
2696
|
+
cards.push(readChange(deps, changesDir, entry.name));
|
|
2697
|
+
}
|
|
2698
|
+
return cards;
|
|
2699
|
+
}
|
|
2700
|
+
function readChange(deps, changesDir, id) {
|
|
2701
|
+
const dir = join10(changesDir, id);
|
|
2702
|
+
const proposal = deps.fsx.readIfExists(join10(dir, "proposal.md"));
|
|
2703
|
+
const design = deps.fsx.readIfExists(join10(dir, "design.md"));
|
|
2704
|
+
const tasks = deps.fsx.readIfExists(join10(dir, "tasks.md"));
|
|
2705
|
+
const specsDir = join10(dir, "specs");
|
|
2706
|
+
const capabilities = deps.fsx.exists(specsDir) ? deps.fsx.readdir(specsDir).filter((e) => e.directory && deps.fsx.exists(join10(specsDir, e.name, "spec.md"))).map((e) => e.name).sort() : [];
|
|
2707
|
+
const counts = taskCounts(tasks);
|
|
2708
|
+
const states = {
|
|
2709
|
+
proposal: proposalState(proposal),
|
|
2710
|
+
design: designState(design),
|
|
2711
|
+
// Deltas have no middle state either.
|
|
2712
|
+
deltas: capabilities.length > 0 ? "complete" : "absent",
|
|
2713
|
+
tasks: counts.total === 0 ? "absent" : counts.done === counts.total ? "complete" : "partial"
|
|
2714
|
+
};
|
|
2715
|
+
return {
|
|
2716
|
+
id,
|
|
2717
|
+
title: titleOf(proposal, id),
|
|
2718
|
+
snippet: snippetOf(proposal),
|
|
2719
|
+
capabilities,
|
|
2720
|
+
...states,
|
|
2721
|
+
tasksDone: counts.done,
|
|
2722
|
+
tasksTotal: counts.total,
|
|
2723
|
+
tasksOpen: counts.total - counts.done,
|
|
2724
|
+
// Derived from the four states rather than re-read, so "empty" cannot disagree with
|
|
2725
|
+
// the indicators sitting next to it on the same card.
|
|
2726
|
+
empty: Object.values(states).every((state) => state === "absent")
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
function deleteChange(deps, id) {
|
|
2730
|
+
const name = id.trim();
|
|
2731
|
+
if (name === "") return { outcome: "refused", reason: "no change was named" };
|
|
2732
|
+
if (name === "archive") {
|
|
2733
|
+
return { outcome: "refused", reason: "the archive is not a change and cannot be deleted" };
|
|
2734
|
+
}
|
|
2735
|
+
if (name.startsWith(".") || /[/\\]/.test(name) || name.split(".").includes("..")) {
|
|
2736
|
+
return { outcome: "refused", reason: `\`${name}\` is not a change directory name` };
|
|
2737
|
+
}
|
|
2738
|
+
const dir = join10(deps.root, "changes", name);
|
|
2739
|
+
if (!deps.fsx.exists(dir)) {
|
|
2740
|
+
return { outcome: "refused", reason: `\`${name}\` has no directory under openspec/changes/` };
|
|
2741
|
+
}
|
|
2742
|
+
deps.fsx.removeDir(dir);
|
|
2743
|
+
return { outcome: "deleted" };
|
|
2744
|
+
}
|
|
2745
|
+
function readChangePreview(deps, id) {
|
|
2746
|
+
const dir = join10(deps.root, "changes", id);
|
|
2747
|
+
if (!deps.fsx.exists(dir)) return null;
|
|
2748
|
+
const documents = ["proposal.md", "design.md", "tasks.md"].map((path) => ({
|
|
2749
|
+
path,
|
|
2750
|
+
content: deps.fsx.readIfExists(join10(dir, path)) ?? null
|
|
2751
|
+
}));
|
|
2752
|
+
const specsDir = join10(dir, "specs");
|
|
2753
|
+
if (deps.fsx.exists(specsDir)) {
|
|
2754
|
+
for (const entry of deps.fsx.readdir(specsDir).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2755
|
+
if (!entry.directory) continue;
|
|
2756
|
+
const path = `specs/${entry.name}/spec.md`;
|
|
2757
|
+
const content = deps.fsx.readIfExists(join10(specsDir, entry.name, "spec.md"));
|
|
2758
|
+
if (content !== void 0) documents.push({ path, content });
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
id,
|
|
2763
|
+
title: titleOf(deps.fsx.readIfExists(join10(dir, "proposal.md")), id),
|
|
2764
|
+
// The real id, never a placeholder: a command the reader has to edit is one they
|
|
2765
|
+
// will mistype.
|
|
2766
|
+
command: `specsketch ${id}`,
|
|
2767
|
+
documents
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
// server/roadmap.ts
|
|
2772
|
+
var ROW_ID = /^\|[^|]*\|\s*`([a-z0-9][a-z0-9-]*)`\s*\|/;
|
|
2773
|
+
function readOrder(roadmap) {
|
|
2774
|
+
const rows = [];
|
|
2775
|
+
roadmap.split("\n").forEach((text, line) => {
|
|
2776
|
+
const match = ROW_ID.exec(text);
|
|
2777
|
+
if (match?.[1]) rows.push({ id: match[1], line, text });
|
|
2778
|
+
});
|
|
2779
|
+
return { rows, order: rows.map((r) => r.id) };
|
|
2780
|
+
}
|
|
2781
|
+
function reorder(roadmap, id, beforeId) {
|
|
2782
|
+
const lines = roadmap.split("\n");
|
|
2783
|
+
const { rows } = readOrder(roadmap);
|
|
2784
|
+
const moving = rows.find((r) => r.id === id);
|
|
2785
|
+
if (!moving) return { outcome: "refused", reason: `\`${id}\` has no row in the roadmap` };
|
|
2786
|
+
if (beforeId !== null && !rows.some((r) => r.id === beforeId)) {
|
|
2787
|
+
return { outcome: "refused", reason: `\`${beforeId}\` has no row in the roadmap` };
|
|
2788
|
+
}
|
|
2789
|
+
if (id === beforeId) {
|
|
2790
|
+
return { outcome: "refused", reason: "a change cannot be moved before itself" };
|
|
2791
|
+
}
|
|
2792
|
+
const row = lines[moving.line];
|
|
2793
|
+
lines.splice(moving.line, 1);
|
|
2794
|
+
let insertAt;
|
|
2795
|
+
if (beforeId === null) {
|
|
2796
|
+
const remaining = readOrder(lines.join("\n")).rows;
|
|
2797
|
+
const last = remaining[remaining.length - 1];
|
|
2798
|
+
insertAt = last === void 0 ? lines.length : last.line + 1;
|
|
2799
|
+
} else {
|
|
2800
|
+
const target = readOrder(lines.join("\n")).rows.find((r) => r.id === beforeId);
|
|
2801
|
+
if (!target) return { outcome: "refused", reason: `\`${beforeId}\` vanished while reordering` };
|
|
2802
|
+
insertAt = target.line;
|
|
2803
|
+
}
|
|
2804
|
+
lines.splice(insertAt, 0, row);
|
|
2805
|
+
const content = lines.join("\n");
|
|
2806
|
+
const before = roadmap.split("\n");
|
|
2807
|
+
const withoutMoved = (all) => {
|
|
2808
|
+
const copy = [...all];
|
|
2809
|
+
const at = copy.indexOf(row);
|
|
2810
|
+
if (at >= 0) copy.splice(at, 1);
|
|
2811
|
+
return copy;
|
|
2812
|
+
};
|
|
2813
|
+
const kept = withoutMoved(before);
|
|
2814
|
+
const now = withoutMoved(lines);
|
|
2815
|
+
if (kept.length !== now.length || kept.some((line, i) => line !== now[i])) {
|
|
2816
|
+
return {
|
|
2817
|
+
outcome: "refused",
|
|
2818
|
+
reason: "the reorder would have changed a line it did not move"
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
return { outcome: "reordered", content };
|
|
2822
|
+
}
|
|
2823
|
+
function cellsOf2(line) {
|
|
2824
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
|
|
2825
|
+
}
|
|
2826
|
+
function isSeparator(line) {
|
|
2827
|
+
return line.trim().startsWith("|") && cellsOf2(line).every((c) => /^\s*:?-+:?\s*$/.test(c));
|
|
2828
|
+
}
|
|
2829
|
+
function governingHeader(lines, index) {
|
|
2830
|
+
for (let i = Math.min(index, lines.length) - 1; i >= 1; i -= 1) {
|
|
2831
|
+
if (!isSeparator(lines[i])) continue;
|
|
2832
|
+
return (lines[i - 1] ?? "").trim().startsWith("|") ? i - 1 : null;
|
|
2833
|
+
}
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
function addRow(roadmap, id) {
|
|
2837
|
+
const lines = roadmap.split("\n");
|
|
2838
|
+
const { rows } = readOrder(roadmap);
|
|
2839
|
+
if (rows.some((r) => r.id === id)) {
|
|
2840
|
+
return { outcome: "refused", reason: `\`${id}\` already has a roadmap row` };
|
|
2841
|
+
}
|
|
2842
|
+
const firstSeparator = lines.findIndex(isSeparator);
|
|
2843
|
+
const insertAt = rows.length > 0 ? rows[rows.length - 1].line + 1 : firstSeparator + 1;
|
|
2844
|
+
const headerAt = governingHeader(lines, insertAt);
|
|
2845
|
+
if (headerAt === null) {
|
|
2846
|
+
return { outcome: "refused", reason: "the roadmap carries no table to append a row to" };
|
|
2847
|
+
}
|
|
2848
|
+
const header = cellsOf2(lines[headerAt]).map((c) => c.trim());
|
|
2849
|
+
const rowsHere = rows.filter((r) => governingHeader(lines, r.line) === headerAt);
|
|
2850
|
+
const idColumn = rowsHere.length > 0 ? cellsOf2(rowsHere[0].text).findIndex((c) => /^\s*`[^`]+`\s*$/.test(c)) : header.findIndex((h) => /change[- ]?id|^id$/i.test(h));
|
|
2851
|
+
if (idColumn < 0) {
|
|
2852
|
+
return { outcome: "refused", reason: "the roadmap table declares no change-id column" };
|
|
2853
|
+
}
|
|
2854
|
+
const cells = header.map(() => "");
|
|
2855
|
+
cells[idColumn] = `\`${id}\``;
|
|
2856
|
+
if (/^#$/.test(header[0] ?? "") && idColumn !== 0) {
|
|
2857
|
+
const written = rows.map((r) => Number.parseInt((cellsOf2(r.text)[0] ?? "").trim(), 10)).filter((n) => Number.isFinite(n));
|
|
2858
|
+
cells[0] = String(written.length > 0 ? Math.max(...written) + 1 : rows.length + 1);
|
|
2859
|
+
}
|
|
2860
|
+
const row = `| ${cells.join(" | ")} |`;
|
|
2861
|
+
lines.splice(insertAt, 0, row);
|
|
2862
|
+
const content = lines.join("\n");
|
|
2863
|
+
const before = roadmap.split("\n");
|
|
2864
|
+
const after = lines.filter((_, i) => i !== insertAt);
|
|
2865
|
+
if (before.length !== after.length || before.some((line, i) => line !== after[i])) {
|
|
2866
|
+
return { outcome: "refused", reason: "the append would have changed a line it did not add" };
|
|
2867
|
+
}
|
|
2868
|
+
if (cellsOf2(row).length !== header.length) {
|
|
2869
|
+
return {
|
|
2870
|
+
outcome: "refused",
|
|
2871
|
+
reason: `the appended row would carry ${cellsOf2(row).length} cells in a ${header.length}-column table`
|
|
2872
|
+
};
|
|
2873
|
+
}
|
|
2874
|
+
return { outcome: "reordered", content };
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2573
2877
|
// server/tools.ts
|
|
2574
2878
|
import { z as z4 } from "zod";
|
|
2575
2879
|
var CreateSpecInput = z4.object({ title: z4.string().min(1) });
|
|
@@ -2774,6 +3078,68 @@ function createApp(anthropic, workspace, store, options) {
|
|
|
2774
3078
|
const powerProfile = readMostlyProfile("specplan-assistant");
|
|
2775
3079
|
const app = new Hono();
|
|
2776
3080
|
app.get("/api/health", (c) => c.json({ ok: true }));
|
|
3081
|
+
if (store) {
|
|
3082
|
+
const roadmapPath = () => {
|
|
3083
|
+
const beside = join11(dirname6(store.dir), "roadmap.md");
|
|
3084
|
+
return store.fsx.exists(beside) ? beside : join11(store.dir, "roadmap.md");
|
|
3085
|
+
};
|
|
3086
|
+
app.get("/api/gallery", (c) => {
|
|
3087
|
+
const cards = listChanges({ fsx: store.fsx, root: store.dir });
|
|
3088
|
+
const roadmap = store.fsx.readIfExists(roadmapPath());
|
|
3089
|
+
const order = roadmap === void 0 ? [] : readOrder(roadmap).order;
|
|
3090
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
3091
|
+
const roadmapped = cards.filter((card) => rank.has(card.id)).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
3092
|
+
const backlog2 = cards.filter((card) => !rank.has(card.id));
|
|
3093
|
+
const present = new Set(cards.map((card) => card.id));
|
|
3094
|
+
const archive = archivedIds({ fsx: store.fsx, root: store.dir });
|
|
3095
|
+
const absent = order.filter((id) => !present.has(id));
|
|
3096
|
+
const archived = absent.filter((id) => archive.has(id));
|
|
3097
|
+
const missing = absent.filter((id) => !archive.has(id));
|
|
3098
|
+
return c.json({
|
|
3099
|
+
roadmap: roadmapped,
|
|
3100
|
+
backlog: backlog2,
|
|
3101
|
+
archived,
|
|
3102
|
+
missing,
|
|
3103
|
+
hasRoadmap: roadmap !== void 0
|
|
3104
|
+
});
|
|
3105
|
+
});
|
|
3106
|
+
app.get("/api/gallery/:id", (c) => {
|
|
3107
|
+
const preview = readChangePreview({ fsx: store.fsx, root: store.dir }, c.req.param("id"));
|
|
3108
|
+
if (preview === null) return c.json({ error: "no-such-change" }, 404);
|
|
3109
|
+
return c.json(preview);
|
|
3110
|
+
});
|
|
3111
|
+
app.post("/api/gallery/roadmap", async (c) => {
|
|
3112
|
+
const body = await c.req.json();
|
|
3113
|
+
const path = roadmapPath();
|
|
3114
|
+
const roadmap = store.fsx.readIfExists(path);
|
|
3115
|
+
if (roadmap === void 0) {
|
|
3116
|
+
return c.json({ outcome: "refused", reason: "no roadmap.md to add to" }, 409);
|
|
3117
|
+
}
|
|
3118
|
+
const result = addRow(roadmap, body.id ?? "");
|
|
3119
|
+
if (result.outcome !== "reordered") return c.json(result, 409);
|
|
3120
|
+
store.fsx.writeAtomicFile(path, result.content);
|
|
3121
|
+
return c.json({ outcome: "added" });
|
|
3122
|
+
});
|
|
3123
|
+
app.delete("/api/gallery/:id", (c) => {
|
|
3124
|
+
const id = c.req.param("id");
|
|
3125
|
+
const result = deleteChange({ fsx: store.fsx, root: store.dir }, id);
|
|
3126
|
+
if (result.outcome !== "deleted") return c.json(result, 409);
|
|
3127
|
+
store.removeProposal(id);
|
|
3128
|
+
return c.json(result);
|
|
3129
|
+
});
|
|
3130
|
+
app.post("/api/gallery/reorder", async (c) => {
|
|
3131
|
+
const body = await c.req.json();
|
|
3132
|
+
const path = roadmapPath();
|
|
3133
|
+
const roadmap = store.fsx.readIfExists(path);
|
|
3134
|
+
if (roadmap === void 0) {
|
|
3135
|
+
return c.json({ outcome: "refused", reason: "no roadmap.md to reorder" }, 409);
|
|
3136
|
+
}
|
|
3137
|
+
const result = reorder(roadmap, body.id ?? "", body.before ?? null);
|
|
3138
|
+
if (result.outcome !== "reordered") return c.json(result, 409);
|
|
3139
|
+
store.fsx.writeAtomicFile(path, result.content);
|
|
3140
|
+
return c.json({ outcome: "reordered" });
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
2777
3143
|
app.get(
|
|
2778
3144
|
"/api/config",
|
|
2779
3145
|
(c) => c.json({
|
|
@@ -2997,7 +3363,7 @@ import { parseArgs } from "node:util";
|
|
|
2997
3363
|
// ../shared/src/cli-config.ts
|
|
2998
3364
|
import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
|
|
2999
3365
|
import { homedir as homedir2, hostname } from "node:os";
|
|
3000
|
-
import { join as
|
|
3366
|
+
import { join as join12, dirname as dirname7 } from "node:path";
|
|
3001
3367
|
|
|
3002
3368
|
// ../shared/src/schemas.ts
|
|
3003
3369
|
import { z as z5 } from "zod";
|
|
@@ -3196,8 +3562,8 @@ var poolCancelInputSchema = z5.object({
|
|
|
3196
3562
|
});
|
|
3197
3563
|
|
|
3198
3564
|
// ../shared/src/cli-config.ts
|
|
3199
|
-
var DEFAULT_CONFIG_DIR =
|
|
3200
|
-
var DEFAULT_CONFIG_PATH =
|
|
3565
|
+
var DEFAULT_CONFIG_DIR = join12(homedir2(), ".coopcli");
|
|
3566
|
+
var DEFAULT_CONFIG_PATH = join12(DEFAULT_CONFIG_DIR, "config.json");
|
|
3201
3567
|
var PROD_API_URL = "https://api.coopcli.com";
|
|
3202
3568
|
function emptyProfile(apiUrl) {
|
|
3203
3569
|
return {
|
|
@@ -3243,7 +3609,7 @@ var ConfigStore = class {
|
|
|
3243
3609
|
configDir;
|
|
3244
3610
|
constructor(configPath) {
|
|
3245
3611
|
this.configPath = configPath ?? DEFAULT_CONFIG_PATH;
|
|
3246
|
-
this.configDir =
|
|
3612
|
+
this.configDir = dirname7(this.configPath);
|
|
3247
3613
|
}
|
|
3248
3614
|
/**
|
|
3249
3615
|
* Select the profile this store operates on for the rest of the process
|
|
@@ -3620,14 +3986,15 @@ function deriveApiUrl(webUrl) {
|
|
|
3620
3986
|
async function login(input, deps) {
|
|
3621
3987
|
const state = randomBytes(16).toString("hex");
|
|
3622
3988
|
const machineId = input.machine ?? hostname2();
|
|
3989
|
+
const log = deps.log ?? ((message) => void process.stderr.write(message));
|
|
3623
3990
|
const server = await startLocalhostServer({ expectedState: state, loginCommand: input.loginCommand });
|
|
3624
3991
|
try {
|
|
3625
3992
|
const webUrl = input.webUrl ?? "https://coopcli.com";
|
|
3626
3993
|
const authUrl = `${webUrl}/cli-auth?port=${server.port}&state=${state}&machine=${encodeURIComponent(machineId)}`;
|
|
3627
|
-
|
|
3994
|
+
log(`Opening browser for authentication...
|
|
3628
3995
|
`);
|
|
3629
3996
|
await deps.openBrowser(authUrl);
|
|
3630
|
-
|
|
3997
|
+
log(`Waiting for callback on port ${server.port}...
|
|
3631
3998
|
`);
|
|
3632
3999
|
const params = await server.waitForCallback();
|
|
3633
4000
|
const displayName = resolveDisplayName(params.displayName, params.email, params.userId);
|
|
@@ -3644,7 +4011,7 @@ async function login(input, deps) {
|
|
|
3644
4011
|
});
|
|
3645
4012
|
const config = await deps.config.load();
|
|
3646
4013
|
if (params.apiUrl && config.apiUrl && params.apiUrl !== config.apiUrl) {
|
|
3647
|
-
|
|
4014
|
+
log(
|
|
3648
4015
|
`
|
|
3649
4016
|
Backend mismatch: you authenticated against ${params.apiUrl}
|
|
3650
4017
|
but your config points to ${config.apiUrl}
|
|
@@ -3762,7 +4129,7 @@ function validateCommand(rootDir, log = console.log, logError = console.error) {
|
|
|
3762
4129
|
}
|
|
3763
4130
|
|
|
3764
4131
|
// server/workspace.ts
|
|
3765
|
-
import { join as
|
|
4132
|
+
import { join as join13, resolve as resolve5 } from "node:path";
|
|
3766
4133
|
var SIDECAR_FILE = "specplan-ui.json";
|
|
3767
4134
|
var PlanWorkspace = class {
|
|
3768
4135
|
/** Resolved openspec root directory. */
|
|
@@ -3776,7 +4143,7 @@ var PlanWorkspace = class {
|
|
|
3776
4143
|
}
|
|
3777
4144
|
// ── Sidecar ──────────────────────────────────────────────────────────────
|
|
3778
4145
|
readSidecar() {
|
|
3779
|
-
const raw = this.fsx.readIfExists(
|
|
4146
|
+
const raw = this.fsx.readIfExists(join13(this.dir, SIDECAR_FILE));
|
|
3780
4147
|
if (!raw) return {};
|
|
3781
4148
|
try {
|
|
3782
4149
|
return JSON.parse(raw);
|
|
@@ -3791,7 +4158,7 @@ var PlanWorkspace = class {
|
|
|
3791
4158
|
delete next.positions;
|
|
3792
4159
|
next.createdAt ??= now;
|
|
3793
4160
|
next.updatedAt = now;
|
|
3794
|
-
this.fsx.writeAtomicFile(
|
|
4161
|
+
this.fsx.writeAtomicFile(join13(this.dir, SIDECAR_FILE), JSON.stringify(next, null, 2));
|
|
3795
4162
|
}
|
|
3796
4163
|
readModel() {
|
|
3797
4164
|
return this.readSidecar().model;
|
|
@@ -3839,7 +4206,9 @@ var PlanWorkspace = class {
|
|
|
3839
4206
|
};
|
|
3840
4207
|
|
|
3841
4208
|
// server/cli.ts
|
|
4209
|
+
var CLI_VERSION = true ? "2026.08.17.2" : "dev";
|
|
3842
4210
|
var USAGE = `Usage: specplan [rootDir] [--port <n>]
|
|
4211
|
+
specplan --version
|
|
3843
4212
|
specplan generate <specId> [rootDir]
|
|
3844
4213
|
specplan apply <proposalId> [rootDir] [--confirm-hand-edit]
|
|
3845
4214
|
specplan validate [rootDir]
|
|
@@ -3974,15 +4343,15 @@ var MIME = {
|
|
|
3974
4343
|
".woff": "font/woff"
|
|
3975
4344
|
};
|
|
3976
4345
|
function resolveClientDir(moduleUrl) {
|
|
3977
|
-
const here =
|
|
4346
|
+
const here = dirname8(fileURLToPath(moduleUrl));
|
|
3978
4347
|
const candidates = [
|
|
3979
|
-
|
|
4348
|
+
join14(here, "..", "client"),
|
|
3980
4349
|
// packed: dist/cli -> dist/client
|
|
3981
|
-
|
|
4350
|
+
join14(here, "..", "dist", "client")
|
|
3982
4351
|
// repo: server/ -> dist/client
|
|
3983
4352
|
];
|
|
3984
4353
|
for (const dir of candidates) {
|
|
3985
|
-
if (existsSync7(
|
|
4354
|
+
if (existsSync7(join14(dir, "index.html"))) return dir;
|
|
3986
4355
|
}
|
|
3987
4356
|
return null;
|
|
3988
4357
|
}
|
|
@@ -3990,7 +4359,7 @@ function staticResponse(clientDir, pathname) {
|
|
|
3990
4359
|
const rel = normalize2(decodeURIComponent(pathname)).replace(/^\/+/, "");
|
|
3991
4360
|
const target = resolve6(clientDir, rel === "" ? "index.html" : rel);
|
|
3992
4361
|
if (!target.startsWith(resolve6(clientDir))) return null;
|
|
3993
|
-
const file = existsSync7(target) && extname(target) ? target :
|
|
4362
|
+
const file = existsSync7(target) && extname(target) ? target : join14(clientDir, "index.html");
|
|
3994
4363
|
if (!existsSync7(file)) return null;
|
|
3995
4364
|
return new Response(readFileSync5(file), {
|
|
3996
4365
|
headers: { "Content-Type": MIME[extname(file)] ?? "application/octet-stream" }
|
|
@@ -4008,7 +4377,7 @@ function detectCredentialSource(env = process.env, home = homedir3()) {
|
|
|
4008
4377
|
if (env.ANTHROPIC_API_KEY) return "ANTHROPIC_API_KEY";
|
|
4009
4378
|
if (env.ANTHROPIC_AUTH_TOKEN) return "ANTHROPIC_AUTH_TOKEN";
|
|
4010
4379
|
try {
|
|
4011
|
-
const dir =
|
|
4380
|
+
const dir = join14(home, ".config", "anthropic", "credentials");
|
|
4012
4381
|
if (readdirSync5(dir).some((f) => f.endsWith(".json"))) return "anthropic profile";
|
|
4013
4382
|
} catch {
|
|
4014
4383
|
}
|
|
@@ -4023,6 +4392,10 @@ function preflight(log = console.log) {
|
|
|
4023
4392
|
}
|
|
4024
4393
|
}
|
|
4025
4394
|
async function main(argv) {
|
|
4395
|
+
if (argv[0] === "--version") {
|
|
4396
|
+
console.log(CLI_VERSION);
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
4026
4399
|
preflight();
|
|
4027
4400
|
if (argv[0] === "login") {
|
|
4028
4401
|
let loginArgs;
|
package/dist/client/assets/{abnfDiagram-VRR7QNED-DYWyHZ9F.js → abnfDiagram-VRR7QNED-BYxvjjLu.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{t}from"./src-
|
|
1
|
+
import{n as e}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{t}from"./src-B56W74gN.js";import"./chunk-WYO6CB5R-qHfws5Na.js";import"./chunk-VAUOI2AC-D7taetBr.js";import{m as n,t as r}from"./mermaid-parser.core-CWtD7LBQ.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-Bio7zOcN.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";var c=n().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{o.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new r(n);let i=n.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-KEIR6QF5-BfrZ3jm6.js";import{M as e}from"./mermaid-parser.core-CWtD7LBQ.js";export{e as createArchitectureServices};
|