plan-assistant 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/client/_app/immutable/chunks/{BJs6I7qC.js → 10ZXP06Q.js} +1 -1
- package/build/client/_app/immutable/chunks/{ChJyFfSj.js → 1eL0zcBS.js} +1 -1
- package/build/client/_app/immutable/chunks/74GYr5Jz.js +3 -0
- package/build/client/_app/immutable/chunks/{B2fFwvWw.js → B-HTmPy1.js} +1 -1
- package/build/client/_app/immutable/chunks/{RIGBV7Ip.js → B2hVYoEJ.js} +1 -1
- package/build/client/_app/immutable/chunks/{DVXd8vwC.js → B628eJGB.js} +1 -1
- package/build/client/_app/immutable/chunks/{CpucjHJL.js → B80ongyB.js} +1 -1
- package/build/client/_app/immutable/chunks/{BXCtgyOT.js → B8FhIQG1.js} +1 -1
- package/build/client/_app/immutable/chunks/{DoIz7SoC.js → BCgSWfby.js} +1 -1
- package/build/client/_app/immutable/chunks/{B-25Ue8o.js → BFl4ofgi.js} +4 -4
- package/build/client/_app/immutable/chunks/{DK1zqZrn.js → BIKGXasq.js} +1 -1
- package/build/client/_app/immutable/chunks/{DlbZX2EZ.js → BNv9nR-i.js} +1 -1
- package/build/client/_app/immutable/chunks/{CeIvvAVV.js → BOjlNXC_.js} +1 -1
- package/build/client/_app/immutable/chunks/{a5eoTq-5.js → BRBMkxQw.js} +1 -1
- package/build/client/_app/immutable/chunks/{_2rlQOMN.js → BT3ImTYg.js} +1 -1
- package/build/client/_app/immutable/chunks/{BJxAjYQA.js → BUu_XsOv.js} +1 -1
- package/build/client/_app/immutable/chunks/{B9WViv7H.js → BVq6bo_n.js} +1 -1
- package/build/client/_app/immutable/chunks/{DteI5BLO.js → BZQqs639.js} +1 -1
- package/build/client/_app/immutable/chunks/{nFhgrnCT.js → BZTb6H6F.js} +1 -1
- package/build/client/_app/immutable/chunks/{Dl-inTne.js → BrjAEg64.js} +1 -1
- package/build/client/_app/immutable/chunks/{DSIQf7-a.js → Bt3MWn7w.js} +26 -26
- package/build/client/_app/immutable/chunks/{Dnz2r_pS.js → BvzznBFq.js} +1 -1
- package/build/client/_app/immutable/chunks/{DshYkidx.js → C4gCk_ik.js} +1 -1
- package/build/client/_app/immutable/chunks/{2wZlHG5k.js → C5wtj4WQ.js} +1 -1
- package/build/client/_app/immutable/chunks/{Dvli4fnO.js → CEqaJ7Yk.js} +1 -1
- package/build/client/_app/immutable/chunks/{3E04qp8C.js → CIGAXayx.js} +1 -1
- package/build/client/_app/immutable/chunks/COggdvXA.js +1 -0
- package/build/client/_app/immutable/chunks/{C47KpwkC.js → CYQe9sJG.js} +1 -1
- package/build/client/_app/immutable/chunks/{BrX0OiHa.js → CYxrEvMN.js} +1 -1
- package/build/client/_app/immutable/chunks/{CUqNPCjK.js → CaLUaLKT.js} +1 -1
- package/build/client/_app/immutable/chunks/{Cwd5DdSa.js → CdHvJ4_w.js} +1 -1
- package/build/client/_app/immutable/chunks/{CJ-lpX9h.js → Ckyou5Od.js} +1 -1
- package/build/client/_app/immutable/chunks/{C1YAjCKh.js → CmrXQ7MQ.js} +1 -1
- package/build/client/_app/immutable/chunks/{BWYga5zO.js → Cn53TYFN.js} +1 -1
- package/build/client/_app/immutable/chunks/{CD_kQCcy.js → CobJHAAW.js} +1 -1
- package/build/client/_app/immutable/chunks/{B51GwAfL.js → CpHaxpYy.js} +1 -1
- package/build/client/_app/immutable/chunks/{BdoKnygS.js → CuD0M7ml.js} +2 -2
- package/build/client/_app/immutable/chunks/{BfABZbDO.js → DGn8gxMH.js} +1 -1
- package/build/client/_app/immutable/chunks/{CbTVVa30.js → DJ4TnckQ.js} +1 -1
- package/build/client/_app/immutable/chunks/{C_qF9Ll1.js → DL-S4jBj.js} +1 -1
- package/build/client/_app/immutable/chunks/{ioQlYLW1.js → DLTGIy2S.js} +1 -1
- package/build/client/_app/immutable/chunks/{EodvFm9d.js → DcaEnsuI.js} +1 -1
- package/build/client/_app/immutable/chunks/{CH1RPLU9.js → DhPQR3Px.js} +1 -1
- package/build/client/_app/immutable/chunks/{gzN3DgKu.js → Dik5m9U4.js} +1 -1
- package/build/client/_app/immutable/chunks/{DSQnKfn-.js → Dj-iuGW3.js} +1 -1
- package/build/client/_app/immutable/chunks/{DePvhy8M.js → MQLxN4-w.js} +1 -1
- package/build/client/_app/immutable/chunks/{SbhBQTvy.js → N5edR_wk.js} +1 -1
- package/build/client/_app/immutable/chunks/{k7j8NQjk.js → NMQ8yb5B.js} +1 -1
- package/build/client/_app/immutable/chunks/{DJe5QTzh.js → S5LArjpJ.js} +1 -1
- package/build/client/_app/immutable/chunks/{CgJVu5ra.js → SsCjYNSL.js} +1 -1
- package/build/client/_app/immutable/chunks/UguYSyR_.js +1 -0
- package/build/client/_app/immutable/chunks/{CkIr3n4I.js → aOKoOpNt.js} +1 -1
- package/build/client/_app/immutable/chunks/{wOfVBGZS.js → dRJp6O4P.js} +1 -1
- package/build/client/_app/immutable/chunks/{CJ4vuw4Q.js → nlEqX7_u.js} +1 -1
- package/build/client/_app/immutable/chunks/{lFo8pWTB.js → q-tJjDlp.js} +1 -1
- package/build/client/_app/immutable/entry/{app.CSk6y__j.js → app.BR7HPYnZ.js} +2 -2
- package/build/client/_app/immutable/entry/start.DbhlkFfd.js +1 -0
- package/build/client/_app/immutable/nodes/0.BDKx-zUI.js +1 -0
- package/build/client/_app/immutable/nodes/{1.Ckq5CDEF.js → 1.B_9JaBUL.js} +1 -1
- package/build/client/_app/immutable/nodes/{2.ZFWOqsMm.js → 2.D_8Y7w6T.js} +1 -1
- package/build/client/_app/immutable/nodes/3.sgWOIIcg.js +1 -0
- package/build/client/_app/version.json +1 -1
- package/build/server/chunks/{0-F8UDIQlw.js → 0-C94QRPbG.js} +2 -2
- package/build/server/chunks/{0-F8UDIQlw.js.map → 0-C94QRPbG.js.map} +1 -1
- package/build/server/chunks/1-G6vwI4Kc.js +9 -0
- package/build/server/chunks/{1-C7yZ906n.js.map → 1-G6vwI4Kc.js.map} +1 -1
- package/build/server/chunks/{2-tND4IT0j.js → 2-Cy9PX2zK.js} +2 -2
- package/build/server/chunks/{2-tND4IT0j.js.map → 2-Cy9PX2zK.js.map} +1 -1
- package/build/server/chunks/{3-DzwYMPfw.js → 3-CxxgkGJa.js} +3 -3
- package/build/server/chunks/{3-DzwYMPfw.js.map → 3-CxxgkGJa.js.map} +1 -1
- package/build/server/chunks/{_page.svelte-BXgDoamA.js → _page.svelte-DdQY1QqH.js} +62 -3
- package/build/server/chunks/_page.svelte-DdQY1QqH.js.map +1 -0
- package/build/server/chunks/_server.ts-CEtvrNT1.js +13 -0
- package/build/server/chunks/_server.ts-CEtvrNT1.js.map +1 -0
- package/build/server/chunks/{_server.ts-p_sHg72N.js → _server.ts-sRxHDQa9.js} +2 -2
- package/build/server/chunks/{_server.ts-p_sHg72N.js.map → _server.ts-sRxHDQa9.js.map} +1 -1
- package/build/server/chunks/{hooks.server-CeNxBnIX.js → hooks.server-sBuk2932.js} +12 -2
- package/build/server/chunks/hooks.server-sBuk2932.js.map +1 -0
- package/build/server/chunks/idle-timer-CouFy_WF.js +49 -0
- package/build/server/chunks/idle-timer-CouFy_WF.js.map +1 -0
- package/build/server/chunks/{sse-manager-DpUiK2jt.js → sse-manager-C9ZLI_Nx.js} +13 -2
- package/build/server/chunks/sse-manager-C9ZLI_Nx.js.map +1 -0
- package/build/server/index.js +22 -3
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +13 -6
- package/build/server/manifest.js.map +1 -1
- package/dist/cli/commands/review.js +36 -233
- package/dist/cli/commands/status.js +12 -4
- package/dist/cli/commands/stop.js +64 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/server-client.js +209 -0
- package/dist/cli/session-reader.js +70 -1
- package/package.json +1 -1
- package/build/client/_app/immutable/chunks/BPkIOP5x.js +0 -1
- package/build/client/_app/immutable/chunks/D8WDJIkf.js +0 -3
- package/build/client/_app/immutable/chunks/QMxLvN9k.js +0 -1
- package/build/client/_app/immutable/entry/start.BnxQc9Fl.js +0 -1
- package/build/client/_app/immutable/nodes/0.BZ6p9IRV.js +0 -1
- package/build/client/_app/immutable/nodes/3.CfL49yTp.js +0 -1
- package/build/server/chunks/1-C7yZ906n.js +0 -9
- package/build/server/chunks/_page.svelte-BXgDoamA.js.map +0 -1
- package/build/server/chunks/hooks.server-CeNxBnIX.js.map +0 -1
- package/build/server/chunks/sse-manager-DpUiK2jt.js.map +0 -1
package/build/server/manifest.js
CHANGED
|
@@ -10,12 +10,12 @@ return {
|
|
|
10
10
|
assets: new Set(["favicon.png"]),
|
|
11
11
|
mimeTypes: {".png":"image/png"},
|
|
12
12
|
_: {
|
|
13
|
-
client: {start:"_app/immutable/entry/start.
|
|
13
|
+
client: {start:"_app/immutable/entry/start.DbhlkFfd.js",app:"_app/immutable/entry/app.BR7HPYnZ.js",imports:["_app/immutable/entry/start.DbhlkFfd.js","_app/immutable/chunks/74GYr5Jz.js","_app/immutable/chunks/CobJHAAW.js","_app/immutable/chunks/DGn8gxMH.js","_app/immutable/entry/app.BR7HPYnZ.js","_app/immutable/chunks/Dj-iuGW3.js","_app/immutable/chunks/CobJHAAW.js","_app/immutable/chunks/dRJp6O4P.js","_app/immutable/chunks/BrjAEg64.js","_app/immutable/chunks/DGn8gxMH.js","_app/immutable/chunks/BZQqs639.js","_app/immutable/chunks/C5wtj4WQ.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
|
|
14
14
|
nodes: [
|
|
15
|
-
__memo(() => import('./chunks/0-
|
|
16
|
-
__memo(() => import('./chunks/1-
|
|
17
|
-
__memo(() => import('./chunks/2-
|
|
18
|
-
__memo(() => import('./chunks/3-
|
|
15
|
+
__memo(() => import('./chunks/0-C94QRPbG.js')),
|
|
16
|
+
__memo(() => import('./chunks/1-G6vwI4Kc.js')),
|
|
17
|
+
__memo(() => import('./chunks/2-Cy9PX2zK.js')),
|
|
18
|
+
__memo(() => import('./chunks/3-CxxgkGJa.js'))
|
|
19
19
|
],
|
|
20
20
|
remotes: {
|
|
21
21
|
|
|
@@ -77,12 +77,19 @@ return {
|
|
|
77
77
|
page: null,
|
|
78
78
|
endpoint: __memo(() => import('./chunks/_server.ts-b4ynvdd4.js'))
|
|
79
79
|
},
|
|
80
|
+
{
|
|
81
|
+
id: "/api/shutdown",
|
|
82
|
+
pattern: /^\/api\/shutdown\/?$/,
|
|
83
|
+
params: [],
|
|
84
|
+
page: null,
|
|
85
|
+
endpoint: __memo(() => import('./chunks/_server.ts-CEtvrNT1.js'))
|
|
86
|
+
},
|
|
80
87
|
{
|
|
81
88
|
id: "/api/sse/[sessionId]",
|
|
82
89
|
pattern: /^\/api\/sse\/([^/]+?)\/?$/,
|
|
83
90
|
params: [{"name":"sessionId","optional":false,"rest":false,"chained":false}],
|
|
84
91
|
page: null,
|
|
85
|
-
endpoint: __memo(() => import('./chunks/_server.ts-
|
|
92
|
+
endpoint: __memo(() => import('./chunks/_server.ts-sRxHDQa9.js'))
|
|
86
93
|
},
|
|
87
94
|
{
|
|
88
95
|
id: "/plan/[sessionId]",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sources":["../../.svelte-kit/adapter-node/manifest.js"],"sourcesContent":["export const manifest = (() => {\nfunction __memo(fn) {\n\tlet value;\n\treturn () => value ??= (value = fn());\n}\n\nreturn {\n\tappDir: \"_app\",\n\tappPath: \"_app\",\n\tassets: new Set([\"favicon.png\"]),\n\tmimeTypes: {\".png\":\"image/png\"},\n\t_: {\n\t\tclient: {start:\"_app/immutable/entry/start.
|
|
1
|
+
{"version":3,"file":"manifest.js","sources":["../../.svelte-kit/adapter-node/manifest.js"],"sourcesContent":["export const manifest = (() => {\nfunction __memo(fn) {\n\tlet value;\n\treturn () => value ??= (value = fn());\n}\n\nreturn {\n\tappDir: \"_app\",\n\tappPath: \"_app\",\n\tassets: new Set([\"favicon.png\"]),\n\tmimeTypes: {\".png\":\"image/png\"},\n\t_: {\n\t\tclient: {start:\"_app/immutable/entry/start.DbhlkFfd.js\",app:\"_app/immutable/entry/app.BR7HPYnZ.js\",imports:[\"_app/immutable/entry/start.DbhlkFfd.js\",\"_app/immutable/chunks/74GYr5Jz.js\",\"_app/immutable/chunks/CobJHAAW.js\",\"_app/immutable/chunks/DGn8gxMH.js\",\"_app/immutable/entry/app.BR7HPYnZ.js\",\"_app/immutable/chunks/Dj-iuGW3.js\",\"_app/immutable/chunks/CobJHAAW.js\",\"_app/immutable/chunks/dRJp6O4P.js\",\"_app/immutable/chunks/BrjAEg64.js\",\"_app/immutable/chunks/DGn8gxMH.js\",\"_app/immutable/chunks/BZQqs639.js\",\"_app/immutable/chunks/C5wtj4WQ.js\"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},\n\t\tnodes: [\n\t\t\t__memo(() => import('./nodes/0.js')),\n\t\t\t__memo(() => import('./nodes/1.js')),\n\t\t\t__memo(() => import('./nodes/2.js')),\n\t\t\t__memo(() => import('./nodes/3.js'))\n\t\t],\n\t\tremotes: {\n\t\t\t\n\t\t},\n\t\troutes: [\n\t\t\t{\n\t\t\t\tid: \"/\",\n\t\t\t\tpattern: /^\\/$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: { layouts: [0,], errors: [1,], leaf: 2 },\n\t\t\t\tendpoint: null\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/health\",\n\t\t\t\tpattern: /^\\/api\\/health\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/health/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions/[sessionId]\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/([^/]+?)\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_sessionId_/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions/[sessionId]/approve\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/([^/]+?)\\/approve\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_sessionId_/approve/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions/[sessionId]/feedback\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/([^/]+?)\\/feedback\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_sessionId_/feedback/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions/[sessionId]/versions\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/([^/]+?)\\/versions\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_sessionId_/versions/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sessions/[sessionId]/versions/[version]\",\n\t\t\t\tpattern: /^\\/api\\/sessions\\/([^/]+?)\\/versions\\/([^/]+?)\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false},{\"name\":\"version\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sessions/_sessionId_/versions/_version_/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/shutdown\",\n\t\t\t\tpattern: /^\\/api\\/shutdown\\/?$/,\n\t\t\t\tparams: [],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/shutdown/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/api/sse/[sessionId]\",\n\t\t\t\tpattern: /^\\/api\\/sse\\/([^/]+?)\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: null,\n\t\t\t\tendpoint: __memo(() => import('./entries/endpoints/api/sse/_sessionId_/_server.ts.js'))\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"/plan/[sessionId]\",\n\t\t\t\tpattern: /^\\/plan\\/([^/]+?)\\/?$/,\n\t\t\t\tparams: [{\"name\":\"sessionId\",\"optional\":false,\"rest\":false,\"chained\":false}],\n\t\t\t\tpage: { layouts: [0,], errors: [1,], leaf: 3 },\n\t\t\t\tendpoint: null\n\t\t\t}\n\t\t],\n\t\tprerendered_routes: new Set([]),\n\t\tmatchers: async () => {\n\t\t\t\n\t\t\treturn { };\n\t\t},\n\t\tserver_assets: {}\n\t}\n}\n})();\n\nexport const prerendered = new Set([]);\n\nexport const base = \"\";"],"names":[],"mappings":"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"}
|
|
@@ -1,179 +1,10 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, } from "node:fs";
|
|
2
2
|
import { resolve, dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { spawn, execSync } from "node:child_process";
|
|
5
|
-
import { createServer } from "node:net";
|
|
6
3
|
import { watch } from "chokidar";
|
|
7
4
|
import { parseMarkdownToPlan, sessionIdFromPath } from "../markdown-to-plan.js";
|
|
8
5
|
import { outputJson } from "../output.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const DEFAULT_BASE_PORT = 5181;
|
|
12
|
-
const MAX_PORT = 5199;
|
|
13
|
-
function getPackageDir() {
|
|
14
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
15
|
-
// dist/cli/commands/review.js -> package root
|
|
16
|
-
return resolve(dirname(thisFile), "../../..");
|
|
17
|
-
}
|
|
18
|
-
async function checkHealth(port) {
|
|
19
|
-
try {
|
|
20
|
-
const controller = new AbortController();
|
|
21
|
-
const timeout = setTimeout(() => controller.abort(), 500);
|
|
22
|
-
const res = await fetch(`http://localhost:${port}/api/health`, {
|
|
23
|
-
signal: controller.signal,
|
|
24
|
-
});
|
|
25
|
-
clearTimeout(timeout);
|
|
26
|
-
if (!res.ok)
|
|
27
|
-
return null;
|
|
28
|
-
return (await res.json());
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function lockFilePath(sessionDir) {
|
|
35
|
-
return join(sessionDir, ".server-lock.json");
|
|
36
|
-
}
|
|
37
|
-
function readLock(sessionDir) {
|
|
38
|
-
const lockPath = lockFilePath(sessionDir);
|
|
39
|
-
if (!existsSync(lockPath))
|
|
40
|
-
return null;
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function writeLock(sessionDir, port, pid) {
|
|
49
|
-
mkdirSync(sessionDir, { recursive: true });
|
|
50
|
-
writeFileSync(lockFilePath(sessionDir), JSON.stringify({ port, pid }));
|
|
51
|
-
}
|
|
52
|
-
function clearLock(sessionDir) {
|
|
53
|
-
try {
|
|
54
|
-
unlinkSync(lockFilePath(sessionDir));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// ignore
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function isPidAlive(pid) {
|
|
61
|
-
try {
|
|
62
|
-
process.kill(pid, 0);
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
async function findExistingServer(sessionDir, basePort) {
|
|
70
|
-
// Check lock file first (fast path, avoids port scan race)
|
|
71
|
-
const lock = readLock(sessionDir);
|
|
72
|
-
if (lock) {
|
|
73
|
-
if (isPidAlive(lock.pid)) {
|
|
74
|
-
const health = await checkHealth(lock.port);
|
|
75
|
-
if (health && health.sessionDir === sessionDir) {
|
|
76
|
-
return lock.port;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// Stale lock — remove it
|
|
80
|
-
clearLock(sessionDir);
|
|
81
|
-
}
|
|
82
|
-
// Fallback: scan ports (handles lock-less legacy servers)
|
|
83
|
-
for (let port = basePort; port <= MAX_PORT; port++) {
|
|
84
|
-
const health = await checkHealth(port);
|
|
85
|
-
if (health && health.sessionDir === sessionDir) {
|
|
86
|
-
return port;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
function isPortFree(port) {
|
|
92
|
-
return new Promise((resolve) => {
|
|
93
|
-
const server = createServer();
|
|
94
|
-
server.once("error", () => resolve(false));
|
|
95
|
-
server.once("listening", () => {
|
|
96
|
-
server.close(() => resolve(true));
|
|
97
|
-
});
|
|
98
|
-
server.listen(port, "127.0.0.1");
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
async function findFreePort(basePort) {
|
|
102
|
-
for (let port = basePort; port <= MAX_PORT; port++) {
|
|
103
|
-
if (await isPortFree(port))
|
|
104
|
-
return port;
|
|
105
|
-
}
|
|
106
|
-
throw new Error(`No free port found in range ${basePort}-${MAX_PORT}`);
|
|
107
|
-
}
|
|
108
|
-
function startServer(sessionDir, port) {
|
|
109
|
-
const packageDir = getPackageDir();
|
|
110
|
-
const buildEntry = join(packageDir, "build", "index.js");
|
|
111
|
-
if (!existsSync(buildEntry)) {
|
|
112
|
-
console.error(`Error: Server build not found at ${buildEntry}\nRun 'npm run build:server' in the plan-assistant package first.`);
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
return new Promise((resolvePromise, reject) => {
|
|
116
|
-
const child = spawn("node", [buildEntry], {
|
|
117
|
-
env: {
|
|
118
|
-
...process.env,
|
|
119
|
-
SESSION_DIR: sessionDir,
|
|
120
|
-
PORT: String(port),
|
|
121
|
-
},
|
|
122
|
-
stdio: "ignore",
|
|
123
|
-
detached: true,
|
|
124
|
-
});
|
|
125
|
-
child.unref();
|
|
126
|
-
const pid = child.pid;
|
|
127
|
-
let attempts = 0;
|
|
128
|
-
const maxAttempts = 30;
|
|
129
|
-
const interval = setInterval(async () => {
|
|
130
|
-
attempts++;
|
|
131
|
-
try {
|
|
132
|
-
const controller = new AbortController();
|
|
133
|
-
const timeout = setTimeout(() => controller.abort(), 1000);
|
|
134
|
-
const res = await fetch(`http://localhost:${port}/api/health`, {
|
|
135
|
-
signal: controller.signal,
|
|
136
|
-
});
|
|
137
|
-
clearTimeout(timeout);
|
|
138
|
-
if (res.ok) {
|
|
139
|
-
clearInterval(interval);
|
|
140
|
-
resolvePromise(pid);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
if (attempts >= maxAttempts) {
|
|
145
|
-
clearInterval(interval);
|
|
146
|
-
reject(new Error("Server failed to start within 15 seconds"));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}, 500);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
async function launchServer(sessionDir, port) {
|
|
153
|
-
process.stdout.write(`Starting Plan Assistant server on port ${port}...`);
|
|
154
|
-
try {
|
|
155
|
-
const pid = await startServer(sessionDir, port);
|
|
156
|
-
writeLock(sessionDir, port, pid);
|
|
157
|
-
console.log(" ready.");
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
console.error(` failed: ${err}`);
|
|
161
|
-
process.exit(1);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function openBrowser(url) {
|
|
165
|
-
try {
|
|
166
|
-
const cmd = process.platform === "darwin"
|
|
167
|
-
? "open"
|
|
168
|
-
: process.platform === "win32"
|
|
169
|
-
? "start"
|
|
170
|
-
: "xdg-open";
|
|
171
|
-
execSync(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
console.log(`Open in browser: ${url}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
6
|
+
import { waitForFeedback } from "../session-reader.js";
|
|
7
|
+
import { DEFAULT_BASE_PORT, MAX_PORT, findExistingServer, checkHealth, isPortFree, findFreePort, launchServer, openBrowser, } from "../server-client.js";
|
|
177
8
|
export async function review(args) {
|
|
178
9
|
const markdownFile = args.positional[0];
|
|
179
10
|
if (!markdownFile) {
|
|
@@ -181,6 +12,11 @@ export async function review(args) {
|
|
|
181
12
|
console.error("Usage: plan-assistant review <markdown-file>");
|
|
182
13
|
process.exit(1);
|
|
183
14
|
}
|
|
15
|
+
// Parse host configuration (for Docker/remote sandbox environments)
|
|
16
|
+
const hostFlag = args.flags.host;
|
|
17
|
+
const displayHost = (typeof hostFlag === "string" ? hostFlag : null) ??
|
|
18
|
+
process.env.PLAN_ASSISTANT_HOST ??
|
|
19
|
+
"localhost";
|
|
184
20
|
// Parse port configuration
|
|
185
21
|
const portFlag = args.flags.port;
|
|
186
22
|
const envPort = process.env.PLAN_ASSISTANT_PORT;
|
|
@@ -274,12 +110,32 @@ Run \`npx plan-assistant init --output <file>\` to generate a correctly-formatte
|
|
|
274
110
|
writeFileSync(join(sessionPath, "plan.json"), JSON.stringify(plan, null, 2));
|
|
275
111
|
writeFileSync(join(sessionPath, "versions", `v${version}.json`), JSON.stringify(plan, null, 2));
|
|
276
112
|
// Find existing server for this session dir or start a new one
|
|
113
|
+
const reuse = args.flags.reuse === true;
|
|
277
114
|
const basePort = requestedPort ?? DEFAULT_BASE_PORT;
|
|
278
115
|
let port = await findExistingServer(sessionDir, basePort);
|
|
116
|
+
if (!port && reuse) {
|
|
117
|
+
// --reuse: find any running plan-assistant server and reuse it
|
|
118
|
+
for (let p = basePort; p <= MAX_PORT; p++) {
|
|
119
|
+
const health = await checkHealth(p);
|
|
120
|
+
if (health) {
|
|
121
|
+
port = p;
|
|
122
|
+
console.error(`Reusing existing server on port ${p}.`);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
279
127
|
if (!port) {
|
|
280
128
|
if (requestedPort) {
|
|
281
129
|
if (!(await isPortFree(requestedPort))) {
|
|
282
|
-
|
|
130
|
+
// Check if it's a plan-assistant server for a better error message
|
|
131
|
+
const health = await checkHealth(requestedPort);
|
|
132
|
+
if (health) {
|
|
133
|
+
console.error(`Error: Port ${requestedPort} is already used by Plan Assistant (session dir: ${health.sessionDir}).` +
|
|
134
|
+
`\nUse \`plan-assistant stop\` to stop it, or add --reuse to share the server.`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.error(`Error: Port ${requestedPort} is already in use by another process.`);
|
|
138
|
+
}
|
|
283
139
|
process.exit(1);
|
|
284
140
|
}
|
|
285
141
|
port = requestedPort;
|
|
@@ -289,16 +145,20 @@ Run \`npx plan-assistant init --output <file>\` to generate a correctly-formatte
|
|
|
289
145
|
}
|
|
290
146
|
await launchServer(sessionDir, port);
|
|
291
147
|
}
|
|
292
|
-
const url = `http
|
|
148
|
+
const url = `http://${displayHost}:${port}/plan/${sessionId}`;
|
|
293
149
|
const feedbackPath = join(sessionPath, "feedback.json");
|
|
294
150
|
// Machine-readable ready event on first line
|
|
295
151
|
outputJson({
|
|
296
152
|
event: "ready",
|
|
297
153
|
sessionId,
|
|
154
|
+
planVersion: version,
|
|
155
|
+
freshCycle: true,
|
|
298
156
|
url,
|
|
299
157
|
feedbackPath,
|
|
300
158
|
});
|
|
301
|
-
|
|
159
|
+
if (displayHost === "localhost") {
|
|
160
|
+
openBrowser(url);
|
|
161
|
+
}
|
|
302
162
|
const noWait = args.flags["no-wait"] === true;
|
|
303
163
|
console.error(`\nPlan Assistant`);
|
|
304
164
|
console.error(` Review: ${url}`);
|
|
@@ -338,7 +198,7 @@ Run \`npx plan-assistant init --output <file>\` to generate a correctly-formatte
|
|
|
338
198
|
});
|
|
339
199
|
// Wait for feedback unless --no-wait
|
|
340
200
|
if (!noWait) {
|
|
341
|
-
await waitForFeedback(feedbackPath, sessionId, plan.meta.title, mdWatcher);
|
|
201
|
+
await waitForFeedback(feedbackPath, sessionId, plan.meta.title, mdWatcher, sessionPath);
|
|
342
202
|
return;
|
|
343
203
|
}
|
|
344
204
|
// Keep process alive (--no-wait mode)
|
|
@@ -348,60 +208,3 @@ Run \`npx plan-assistant init --output <file>\` to generate a correctly-formatte
|
|
|
348
208
|
process.exit(0);
|
|
349
209
|
});
|
|
350
210
|
}
|
|
351
|
-
async function waitForFeedback(feedbackPath, sessionId, planTitle, mdWatcher) {
|
|
352
|
-
// Check if feedback already submitted
|
|
353
|
-
if (existsSync(feedbackPath)) {
|
|
354
|
-
try {
|
|
355
|
-
const existing = JSON.parse(readFileSync(feedbackPath, "utf-8"));
|
|
356
|
-
if (existing.status === "approved" || existing.status === "needs-work") {
|
|
357
|
-
mdWatcher.close();
|
|
358
|
-
outputFeedbackResult(existing, sessionId, planTitle);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
/* ignore */
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return new Promise((resolve) => {
|
|
367
|
-
const fbWatcher = watch(feedbackPath, {
|
|
368
|
-
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
|
|
369
|
-
});
|
|
370
|
-
const check = () => {
|
|
371
|
-
try {
|
|
372
|
-
if (!existsSync(feedbackPath))
|
|
373
|
-
return;
|
|
374
|
-
const data = JSON.parse(readFileSync(feedbackPath, "utf-8"));
|
|
375
|
-
if (data.status === "approved" || data.status === "needs-work") {
|
|
376
|
-
fbWatcher.close();
|
|
377
|
-
mdWatcher.close();
|
|
378
|
-
outputFeedbackResult(data, sessionId, planTitle);
|
|
379
|
-
resolve();
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
catch {
|
|
383
|
-
/* ignore parse errors during writes */
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
fbWatcher.on("change", check);
|
|
387
|
-
fbWatcher.on("add", check);
|
|
388
|
-
process.on("SIGINT", () => {
|
|
389
|
-
fbWatcher.close();
|
|
390
|
-
mdWatcher.close();
|
|
391
|
-
console.error("\nStopped watching.");
|
|
392
|
-
process.exit(0);
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
function outputFeedbackResult(feedback, sessionId, planTitle) {
|
|
397
|
-
const unresolvedComments = feedback.comments.filter((c) => !c.resolved);
|
|
398
|
-
outputJson({
|
|
399
|
-
event: "feedback",
|
|
400
|
-
sessionId,
|
|
401
|
-
planTitle,
|
|
402
|
-
status: feedback.status,
|
|
403
|
-
comments: unresolvedComments,
|
|
404
|
-
commentCount: unresolvedComments.length,
|
|
405
|
-
});
|
|
406
|
-
process.exit(feedback.status === "approved" ? EXIT_APPROVED : EXIT_NEEDS_WORK);
|
|
407
|
-
}
|
|
@@ -67,12 +67,17 @@ export async function status(args) {
|
|
|
67
67
|
await waitForFeedback(resolved.sessionDir, resolved.sessionId, meta, timeoutMs);
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
|
-
const
|
|
70
|
+
const rawFeedback = readFeedback(resolved.sessionDir);
|
|
71
|
+
// Discard feedback from a previous plan version (stale cycle)
|
|
72
|
+
const feedback = rawFeedback && meta.planVersion && rawFeedback.planVersion < meta.planVersion
|
|
73
|
+
? null
|
|
74
|
+
: rawFeedback;
|
|
71
75
|
const { feedbackStatus, exitCode } = computeStatus(feedback);
|
|
72
76
|
const { phaseSummary, commentSummary } = computeSummary(feedback);
|
|
73
77
|
outputJson({
|
|
74
78
|
sessionId: resolved.sessionId,
|
|
75
79
|
planTitle: meta.planTitle,
|
|
80
|
+
planVersion: meta.planVersion,
|
|
76
81
|
status: meta.status,
|
|
77
82
|
feedbackStatus,
|
|
78
83
|
phaseSummary,
|
|
@@ -81,9 +86,11 @@ export async function status(args) {
|
|
|
81
86
|
process.exit(exitCode);
|
|
82
87
|
}
|
|
83
88
|
async function waitForFeedback(sessionDir, sessionId, meta, timeoutMs) {
|
|
84
|
-
// Check current state first
|
|
89
|
+
// Check current state first (ignore stale feedback from older plan versions)
|
|
85
90
|
const current = readFeedback(sessionDir);
|
|
86
|
-
if (current &&
|
|
91
|
+
if (current &&
|
|
92
|
+
current.status !== "reviewing" &&
|
|
93
|
+
!(meta.planVersion && current.planVersion < meta.planVersion)) {
|
|
87
94
|
const { feedbackStatus, exitCode } = computeStatus(current);
|
|
88
95
|
const { phaseSummary, commentSummary } = computeSummary(current);
|
|
89
96
|
outputJson({
|
|
@@ -109,7 +116,8 @@ async function waitForFeedback(sessionDir, sessionId, meta, timeoutMs) {
|
|
|
109
116
|
if (!existsSync(feedbackPath))
|
|
110
117
|
return;
|
|
111
118
|
const data = JSON.parse(readFileSync(feedbackPath, "utf-8"));
|
|
112
|
-
if (data.status !== "reviewing"
|
|
119
|
+
if (data.status !== "reviewing" &&
|
|
120
|
+
!(meta.planVersion && data.planVersion < meta.planVersion)) {
|
|
113
121
|
clearTimeout(timer);
|
|
114
122
|
watcher.close();
|
|
115
123
|
const { feedbackStatus, exitCode } = computeStatus(data);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { resolveSession } from "../session-resolver.js";
|
|
3
|
+
import { stopServer, checkHealth, DEFAULT_BASE_PORT, MAX_PORT, } from "../server-client.js";
|
|
4
|
+
import { outputJson, outputError } from "../output.js";
|
|
5
|
+
export async function stop(args) {
|
|
6
|
+
const target = args.positional[0];
|
|
7
|
+
if (target) {
|
|
8
|
+
// Stop server for a specific session
|
|
9
|
+
const session = resolveSession(target);
|
|
10
|
+
if (!session) {
|
|
11
|
+
outputError(`Session not found: ${target}`, "NOT_FOUND");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const parentDir = dirname(session.sessionDir);
|
|
15
|
+
const stopped = await stopServer(parentDir);
|
|
16
|
+
if (stopped) {
|
|
17
|
+
outputJson({ event: "server-stopped", sessionDir: parentDir });
|
|
18
|
+
console.error(`Server stopped for session ${session.sessionId}.`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
outputJson({ event: "no-server", sessionDir: parentDir });
|
|
22
|
+
console.error(`No running server found for session ${session.sessionId}.`);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// No argument: find and stop all plan-assistant servers in port range
|
|
27
|
+
let stopped = 0;
|
|
28
|
+
const results = [];
|
|
29
|
+
for (let port = DEFAULT_BASE_PORT; port <= MAX_PORT; port++) {
|
|
30
|
+
const health = await checkHealth(port);
|
|
31
|
+
if (health) {
|
|
32
|
+
try {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
35
|
+
await fetch(`http://localhost:${port}/api/shutdown`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
results.push({ port, sessionDir: health.sessionDir });
|
|
41
|
+
stopped++;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Try SIGTERM
|
|
45
|
+
try {
|
|
46
|
+
process.kill(health.pid, "SIGTERM");
|
|
47
|
+
results.push({ port, sessionDir: health.sessionDir });
|
|
48
|
+
stopped++;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (stopped === 0) {
|
|
57
|
+
outputJson({ event: "no-servers", stopped: 0 });
|
|
58
|
+
console.error("No running Plan Assistant servers found.");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
outputJson({ event: "servers-stopped", stopped, servers: results });
|
|
62
|
+
console.error(`Stopped ${stopped} server(s).`);
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -54,6 +54,7 @@ Commands:
|
|
|
54
54
|
plan-assistant status <session-id-or-file> Check review status
|
|
55
55
|
plan-assistant feedback <session-id-or-file> Read feedback JSON
|
|
56
56
|
plan-assistant list [--dir <path>] List all sessions
|
|
57
|
+
plan-assistant stop [<session-id-or-file>] Stop running server(s)
|
|
57
58
|
plan-assistant clean [--all] [--older-than <dur>] Remove old sessions
|
|
58
59
|
plan-assistant export <session-id-or-file> Export as HTML
|
|
59
60
|
plan-assistant help format Show the required plan format
|
|
@@ -62,7 +63,9 @@ Commands:
|
|
|
62
63
|
Flags:
|
|
63
64
|
--pretty Human-readable output (default: JSON)
|
|
64
65
|
--port <N> Port for review server (review command)
|
|
66
|
+
--host <H> Hostname for browser URL, e.g. host IP when running in Docker (review command)
|
|
65
67
|
--no-wait Don't wait for feedback, just start server (review command)
|
|
68
|
+
--reuse Reuse an already-running server on another session (review command)
|
|
66
69
|
--wait Block until feedback is submitted (status command)
|
|
67
70
|
|
|
68
71
|
TIP: Always start with \`plan-assistant init\` to get a correctly-formatted template.
|
|
@@ -150,6 +153,10 @@ export async function main(args) {
|
|
|
150
153
|
const { init } = await import("./commands/init.js");
|
|
151
154
|
return init(parsed);
|
|
152
155
|
}
|
|
156
|
+
case "stop": {
|
|
157
|
+
const { stop } = await import("./commands/stop.js");
|
|
158
|
+
return stop(parsed);
|
|
159
|
+
}
|
|
153
160
|
case "clean": {
|
|
154
161
|
const { clean } = await import("./commands/clean.js");
|
|
155
162
|
return clean(parsed);
|