strikethroo 3.0.2 → 3.2.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/README.md +18 -2
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/serve/archive.d.ts +43 -0
- package/dist/serve/archive.d.ts.map +1 -0
- package/dist/serve/archive.js +124 -0
- package/dist/serve/archive.js.map +1 -0
- package/dist/serve/derivation.d.ts +78 -0
- package/dist/serve/derivation.d.ts.map +1 -0
- package/dist/serve/derivation.js +237 -0
- package/dist/serve/derivation.js.map +1 -0
- package/dist/serve/events.d.ts +41 -0
- package/dist/serve/events.d.ts.map +1 -0
- package/dist/serve/events.js +91 -0
- package/dist/serve/events.js.map +1 -0
- package/dist/serve/markdown.d.ts +75 -0
- package/dist/serve/markdown.d.ts.map +1 -0
- package/dist/serve/markdown.js +221 -0
- package/dist/serve/markdown.js.map +1 -0
- package/dist/serve/root.d.ts +48 -0
- package/dist/serve/root.d.ts.map +1 -0
- package/dist/serve/root.js +110 -0
- package/dist/serve/root.js.map +1 -0
- package/dist/serve/self-review.d.ts +70 -0
- package/dist/serve/self-review.d.ts.map +1 -0
- package/dist/serve/self-review.js +164 -0
- package/dist/serve/self-review.js.map +1 -0
- package/dist/serve/server.d.ts +70 -0
- package/dist/serve/server.d.ts.map +1 -0
- package/dist/serve/server.js +368 -0
- package/dist/serve/server.js.map +1 -0
- package/dist/serve/watcher.d.ts +32 -0
- package/dist/serve/watcher.d.ts.map +1 -0
- package/dist/serve/watcher.js +99 -0
- package/dist/serve/watcher.js.map +1 -0
- package/dist/serve/workspace-model.d.ts +80 -0
- package/dist/serve/workspace-model.d.ts.map +1 -0
- package/dist/serve/workspace-model.js +176 -0
- package/dist/serve/workspace-model.js.map +1 -0
- package/dist/skill-scripts/shared/frontmatter.d.ts +2 -0
- package/dist/skill-scripts/shared/frontmatter.d.ts.map +1 -0
- package/dist/skill-scripts/shared/frontmatter.js +37 -0
- package/dist/skill-scripts/shared/frontmatter.js.map +1 -0
- package/dist/skill-scripts/shared/plan-scan.d.ts +10 -0
- package/dist/skill-scripts/shared/plan-scan.d.ts.map +1 -0
- package/dist/skill-scripts/shared/plan-scan.js +91 -0
- package/dist/skill-scripts/shared/plan-scan.js.map +1 -0
- package/dist/skill-scripts/shared/root.d.ts +2 -0
- package/dist/skill-scripts/shared/root.d.ts.map +1 -0
- package/dist/skill-scripts/shared/root.js +104 -0
- package/dist/skill-scripts/shared/root.js.map +1 -0
- package/dist-web/assets/Fraunces-Italic-VariableFont_SOFT_WONK_opsz_wght-CuXX_kIb.ttf +0 -0
- package/dist-web/assets/Fraunces-VariableFont_SOFT_WONK_opsz_wght-DLWfSgHL.ttf +0 -0
- package/dist-web/assets/Outfit-VariableFont_wght-B6lQlNBM.ttf +0 -0
- package/dist-web/assets/SFMonoBold-DXHgGUoo.otf +0 -0
- package/dist-web/assets/SFMonoBoldItalic-CdGEImHi.otf +0 -0
- package/dist-web/assets/SFMonoHeavy-DL92f7rT.otf +0 -0
- package/dist-web/assets/SFMonoHeavyItalic-DnE00LFH.otf +0 -0
- package/dist-web/assets/SFMonoLight-CaMC1gg-.otf +0 -0
- package/dist-web/assets/SFMonoLightItalic-DG3Owzn7.otf +0 -0
- package/dist-web/assets/SFMonoMedium-ypRDCKSo.otf +0 -0
- package/dist-web/assets/SFMonoMediumItalic-DkJk-c-x.otf +0 -0
- package/dist-web/assets/SFMonoRegular-77Uh1IPk.otf +0 -0
- package/dist-web/assets/SFMonoRegularItalic-Cn01bACm.otf +0 -0
- package/dist-web/assets/SFMonoSemibold-CvhtzcyM.otf +0 -0
- package/dist-web/assets/SFMonoSemiboldItalic-D75NMH13.otf +0 -0
- package/dist-web/assets/arc-JkuhZ_PU.js +1 -0
- package/dist-web/assets/architectureDiagram-3BPJPVTR-DwCgGxAM.js +36 -0
- package/dist-web/assets/blockDiagram-GPEHLZMM-BCwcsKdi.js +132 -0
- package/dist-web/assets/c4Diagram-AAUBKEIU-Cm1cMFQT.js +10 -0
- package/dist-web/assets/channel-8UZd6AbH.js +1 -0
- package/dist-web/assets/chunk-2J33WTMH-BHDreu9q.js +1 -0
- package/dist-web/assets/chunk-4BX2VUAB-CvH1iMKB.js +1 -0
- package/dist-web/assets/chunk-55IACEB6-C02LfbKU.js +1 -0
- package/dist-web/assets/chunk-727SXJPM-BYDaeCJW.js +206 -0
- package/dist-web/assets/chunk-AQP2D5EJ-CW-sdA3M.js +231 -0
- package/dist-web/assets/chunk-FMBD7UC4-BE3hNKOn.js +15 -0
- package/dist-web/assets/chunk-ND2GUHAM-BosLJTdj.js +1 -0
- package/dist-web/assets/chunk-QZHKN3VN-Bf44xttm.js +1 -0
- package/dist-web/assets/classDiagram-4FO5ZUOK-D7aMNqbH.js +1 -0
- package/dist-web/assets/classDiagram-v2-Q7XG4LA2-D7aMNqbH.js +1 -0
- package/dist-web/assets/cose-bilkent-S5V4N54A-BZS-xBec.js +1 -0
- package/dist-web/assets/cytoscape.esm-BHYC38rz.js +331 -0
- package/dist-web/assets/dagre-BM42HDAG-Be_HyeG7.js +4 -0
- package/dist-web/assets/defaultLocale-CrowFXzY.js +1 -0
- package/dist-web/assets/diagram-2AECGRRQ-BVt23uOL.js +43 -0
- package/dist-web/assets/diagram-5GNKFQAL-BF-OZRmP.js +10 -0
- package/dist-web/assets/diagram-KO2AKTUF-BvW-uYc2.js +3 -0
- package/dist-web/assets/diagram-LMA3HP47-CG2JR86G.js +24 -0
- package/dist-web/assets/diagram-OG6HWLK6-PZ5nR1AF.js +24 -0
- package/dist-web/assets/erDiagram-TEJ5UH35-D2GJWnIo.js +85 -0
- package/dist-web/assets/flowDiagram-I6XJVG4X-DGboZXQm.js +162 -0
- package/dist-web/assets/ganttDiagram-6RSMTGT7-IPOlSI7E.js +292 -0
- package/dist-web/assets/gitGraphDiagram-PVQCEYII-DO9E2tXK.js +106 -0
- package/dist-web/assets/graph-Dqkl27Ch.js +1 -0
- package/dist-web/assets/index-B8AJFM1Y.js +125 -0
- package/dist-web/assets/index-Bm5I46eC.css +1 -0
- package/dist-web/assets/infoDiagram-5YYISTIA-Clg2ubQz.js +2 -0
- package/dist-web/assets/init-Gi6I4Gst.js +1 -0
- package/dist-web/assets/ishikawaDiagram-YF4QCWOH-CDyYc7AV.js +70 -0
- package/dist-web/assets/journeyDiagram-JHISSGLW-CIz55Ff7.js +139 -0
- package/dist-web/assets/kanban-definition-UN3LZRKU-OzC_r8lI.js +89 -0
- package/dist-web/assets/katex-C5jXJg4s.js +257 -0
- package/dist-web/assets/layout-DO9UvE1P.js +1 -0
- package/dist-web/assets/linear-rdoP0iOK.js +1 -0
- package/dist-web/assets/mermaid.core-DOgS-uvF.js +307 -0
- package/dist-web/assets/mindmap-definition-RKZ34NQL-52nfuxxC.js +96 -0
- package/dist-web/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist-web/assets/pieDiagram-4H26LBE5-Drwd0WV7.js +30 -0
- package/dist-web/assets/quadrantDiagram-W4KKPZXB-DYJRwtFj.js +7 -0
- package/dist-web/assets/requirementDiagram-4Y6WPE33-HOL8fCwg.js +84 -0
- package/dist-web/assets/sankeyDiagram-5OEKKPKP-CyA-ExUk.js +40 -0
- package/dist-web/assets/sequenceDiagram-3UESZ5HK-C6Zr5XVd.js +162 -0
- package/dist-web/assets/stateDiagram-AJRCARHV-DSAZMis2.js +1 -0
- package/dist-web/assets/stateDiagram-v2-BHNVJYJU-nDV4UH9R.js +1 -0
- package/dist-web/assets/timeline-definition-PNZ67QCA-CUPjxRlk.js +120 -0
- package/dist-web/assets/vennDiagram-CIIHVFJN-CtjMS4vh.js +34 -0
- package/dist-web/assets/wardley-L42UT6IY-Bk6PjBWD.js +173 -0
- package/dist-web/assets/wardleyDiagram-YWT4CUSO-Bjb8yHyW.js +78 -0
- package/dist-web/assets/xychartDiagram-2RQKCTM6-C3ukcEfW.js +7 -0
- package/dist-web/favicon.svg +13 -0
- package/dist-web/index.html +30 -0
- package/package.json +28 -8
- package/templates/harness/skills/st-full-workflow/SKILL.md +2 -2
- package/templates/harness/skills/st-generate-tasks/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/strikethroo)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-

|
|
7
|
-
|
|
8
6
|
**Strikethroo: extensible AI-powered plan and task management with customizable workflows and structured development processes.**
|
|
9
7
|
|
|
10
8
|
Transform complex AI prompts into organized, executable workflows through customizable hooks, templates, and progressive refinement. Works seamlessly within your existing AI subscriptions for Claude Code, Gemini CLI, and Open Code.
|
|
@@ -25,6 +23,24 @@ The skills give your assistant the planning, decomposition, and execution workfl
|
|
|
25
23
|
|
|
26
24
|
The CLI's `init` emits the shared workspace plus harness-specific agents (e.g., `.claude/agents/` for Claude). Other harnesses rely entirely on the installed skills.
|
|
27
25
|
|
|
26
|
+
## 🌐 Serve the Workspace
|
|
27
|
+
|
|
28
|
+
Run a local web app over an initialized `.ai/strikethroo/` workspace:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx strikethroo serve
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`serve` boots a dependency-light Node server that hosts the prebuilt single-page app as static files, exposes a read-only JSON API over the workspace model, and streams a coalesced `changed` event over Server-Sent Events whenever the workspace mutates on disk. Run it from inside an initialized workspace; if none is found it prints guidance to run `init` and exits without binding.
|
|
35
|
+
|
|
36
|
+
The viewer is read-only **except for one permitted mutation: the archive action.** A plan whose tasks are all complete (derived state `done`) shows an **Archive** control; confirming it issues `POST /api/plans/:id/archive`, which moves that plan's directory from `plans/` to `archive/`. It is strictly a directory move — no files are deleted or edited, and only `done` plans are accepted. This is the manual escape hatch for plans that are done but not yet archived; it does **not** replace the automatic archival the `st-execute-blueprint` skill performs on successful completion.
|
|
37
|
+
|
|
38
|
+
Flags:
|
|
39
|
+
|
|
40
|
+
- `--port <n>` — port to bind (default `4317`).
|
|
41
|
+
- `--no-open` — do not open the browser on start.
|
|
42
|
+
- `--workspace <path>` — override workspace root discovery.
|
|
43
|
+
|
|
28
44
|
## ✨ Key Benefits
|
|
29
45
|
|
|
30
46
|
- **🔧 Fully Customizable**: Tailor hooks, templates, and workflows to your project's specific needs
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
const commander_1 = require("commander");
|
|
11
11
|
const index_1 = require("./index");
|
|
12
|
+
const root_1 = require("./serve/root");
|
|
13
|
+
const server_1 = require("./serve/server");
|
|
12
14
|
const program = new commander_1.Command();
|
|
13
15
|
program.name('strikethroo').version('0.1.0').description('AI-powered task management CLI tool');
|
|
14
16
|
program
|
|
@@ -34,6 +36,32 @@ program
|
|
|
34
36
|
process.exit(1);
|
|
35
37
|
}
|
|
36
38
|
});
|
|
39
|
+
program
|
|
40
|
+
.command('serve')
|
|
41
|
+
.description('Serve the workspace as a local web app (static SPA, JSON API, SSE change stream)')
|
|
42
|
+
.option('--port <n>', 'port to bind', '4317')
|
|
43
|
+
.option('--no-open', 'do not open the browser on start')
|
|
44
|
+
.option('--workspace <path>', 'override workspace root discovery')
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
try {
|
|
47
|
+
const resolved = (0, root_1.resolveWorkspaceRoot)({ workspace: opts.workspace });
|
|
48
|
+
if ((0, root_1.isResolveError)(resolved)) {
|
|
49
|
+
console.error(resolved.error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const handle = await (0, server_1.startServer)({
|
|
53
|
+
root: resolved.root,
|
|
54
|
+
port: Number(opts.port),
|
|
55
|
+
open: opts.open,
|
|
56
|
+
assetsDir: (0, server_1.defaultAssetsDir)(),
|
|
57
|
+
});
|
|
58
|
+
console.log(`Serving ${handle.url}`);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.error(`Failed to start serve: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
37
65
|
// Error handling for unknown commands
|
|
38
66
|
program.on('command:*', async (operands) => {
|
|
39
67
|
console.error(`Unknown command: ${operands[0]}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;AACA;;;;;GAKG;;AAEH,yCAAoC;AACpC,mCAA+B;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive operation for `npx strikethroo serve`.
|
|
3
|
+
*
|
|
4
|
+
* The serve web app is a read-only viewer over `.ai/strikethroo/` with exactly
|
|
5
|
+
* one sanctioned mutation: moving a plan that has reached the derived `done`
|
|
6
|
+
* state from `plans/NN--slug/` to `archive/NN--slug/`. This module is that
|
|
7
|
+
* mutation's authoritative guard — a single, HTTP-free function the route
|
|
8
|
+
* handler and the tests both call.
|
|
9
|
+
*
|
|
10
|
+
* It validates three preconditions, in order — the plan exists, currently lives
|
|
11
|
+
* under `plans/` (not already archived), and is `done` per the derived state in
|
|
12
|
+
* `workspace-model.ts` — then performs a single atomic `fs.rename` of the whole
|
|
13
|
+
* plan directory. It never deletes files, never edits file contents, and never
|
|
14
|
+
* falls back to a non-atomic copy-then-delete. Expected precondition failures
|
|
15
|
+
* are returned as a typed result, not thrown; only an unexpected filesystem
|
|
16
|
+
* error surfaces as the `fs-error` variant. Node built-ins only.
|
|
17
|
+
*/
|
|
18
|
+
import { PlanDetail } from './workspace-model';
|
|
19
|
+
/**
|
|
20
|
+
* Outcome of an {@link archivePlan} call. A discriminated union mirroring the
|
|
21
|
+
* serve layer's existing result convention (see `self-review.ts`): callers map
|
|
22
|
+
* `reason` to HTTP status codes without re-deriving anything.
|
|
23
|
+
*/
|
|
24
|
+
export type ArchiveResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
plan: PlanDetail;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
reason: 'not-found' | 'already-archived' | 'not-done' | 'destination-exists' | 'fs-error';
|
|
30
|
+
message: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Validates the archive preconditions for plan `planId` under `root` (the
|
|
34
|
+
* absolute `.ai/strikethroo` directory) and, if they pass, atomically renames
|
|
35
|
+
* its directory from `plans/` into `archive/`, returning the refreshed model.
|
|
36
|
+
*
|
|
37
|
+
* On any precondition failure it performs zero filesystem changes and returns a
|
|
38
|
+
* typed failure with an actionable message. The only write it performs beyond
|
|
39
|
+
* the rename is creating the `archive/` directory if it does not yet exist,
|
|
40
|
+
* which is required for the rename's destination parent to resolve.
|
|
41
|
+
*/
|
|
42
|
+
export declare const archivePlan: (root: string, planId: number) => Promise<ArchiveResult>;
|
|
43
|
+
//# sourceMappingURL=archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/serve/archive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,EAAiB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE9D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC9B;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,WAAW,GAAG,kBAAkB,GAAG,UAAU,GAAG,oBAAoB,GAAG,UAAU,CAAC;IAC1F,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAU,MAAM,MAAM,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC,aAAa,CA6DrF,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Archive operation for `npx strikethroo serve`.
|
|
4
|
+
*
|
|
5
|
+
* The serve web app is a read-only viewer over `.ai/strikethroo/` with exactly
|
|
6
|
+
* one sanctioned mutation: moving a plan that has reached the derived `done`
|
|
7
|
+
* state from `plans/NN--slug/` to `archive/NN--slug/`. This module is that
|
|
8
|
+
* mutation's authoritative guard — a single, HTTP-free function the route
|
|
9
|
+
* handler and the tests both call.
|
|
10
|
+
*
|
|
11
|
+
* It validates three preconditions, in order — the plan exists, currently lives
|
|
12
|
+
* under `plans/` (not already archived), and is `done` per the derived state in
|
|
13
|
+
* `workspace-model.ts` — then performs a single atomic `fs.rename` of the whole
|
|
14
|
+
* plan directory. It never deletes files, never edits file contents, and never
|
|
15
|
+
* falls back to a non-atomic copy-then-delete. Expected precondition failures
|
|
16
|
+
* are returned as a typed result, not thrown; only an unexpected filesystem
|
|
17
|
+
* error surfaces as the `fs-error` variant. Node built-ins only.
|
|
18
|
+
*/
|
|
19
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
22
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
23
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
24
|
+
}
|
|
25
|
+
Object.defineProperty(o, k2, desc);
|
|
26
|
+
}) : (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
o[k2] = m[k];
|
|
29
|
+
}));
|
|
30
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
31
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
32
|
+
}) : function(o, v) {
|
|
33
|
+
o["default"] = v;
|
|
34
|
+
});
|
|
35
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
36
|
+
var ownKeys = function(o) {
|
|
37
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
38
|
+
var ar = [];
|
|
39
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
40
|
+
return ar;
|
|
41
|
+
};
|
|
42
|
+
return ownKeys(o);
|
|
43
|
+
};
|
|
44
|
+
return function (mod) {
|
|
45
|
+
if (mod && mod.__esModule) return mod;
|
|
46
|
+
var result = {};
|
|
47
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
48
|
+
__setModuleDefault(result, mod);
|
|
49
|
+
return result;
|
|
50
|
+
};
|
|
51
|
+
})();
|
|
52
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.archivePlan = void 0;
|
|
54
|
+
const fs = __importStar(require("fs"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const workspace_model_1 = require("./workspace-model");
|
|
57
|
+
/**
|
|
58
|
+
* Validates the archive preconditions for plan `planId` under `root` (the
|
|
59
|
+
* absolute `.ai/strikethroo` directory) and, if they pass, atomically renames
|
|
60
|
+
* its directory from `plans/` into `archive/`, returning the refreshed model.
|
|
61
|
+
*
|
|
62
|
+
* On any precondition failure it performs zero filesystem changes and returns a
|
|
63
|
+
* typed failure with an actionable message. The only write it performs beyond
|
|
64
|
+
* the rename is creating the `archive/` directory if it does not yet exist,
|
|
65
|
+
* which is required for the rename's destination parent to resolve.
|
|
66
|
+
*/
|
|
67
|
+
const archivePlan = async (root, planId) => {
|
|
68
|
+
const detail = (0, workspace_model_1.getPlanDetail)(root, planId);
|
|
69
|
+
if (!detail) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
reason: 'not-found',
|
|
73
|
+
message: `Plan ${planId} was not found.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (detail.archived) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
reason: 'already-archived',
|
|
80
|
+
message: `Plan ${planId} is already archived.`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (detail.state !== 'done') {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
reason: 'not-done',
|
|
87
|
+
message: `Plan ${planId} is not in the done state and cannot be archived.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const src = detail.dir;
|
|
91
|
+
const dest = path.join(root, 'archive', detail.name);
|
|
92
|
+
if (fs.existsSync(dest)) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
reason: 'destination-exists',
|
|
96
|
+
message: `Plan ${planId} cannot be archived: a directory already exists at its archive location.`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
// The destination's parent (`archive/`) must exist for the rename to land.
|
|
101
|
+
// Creating it is benign and never touches plan contents.
|
|
102
|
+
await fs.promises.mkdir(path.join(root, 'archive'), { recursive: true });
|
|
103
|
+
await fs.promises.rename(src, dest);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
reason: 'fs-error',
|
|
109
|
+
message: `Plan ${planId} could not be archived due to a filesystem error.`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// Re-resolve so the returned model reflects the new archive/ location.
|
|
113
|
+
const moved = (0, workspace_model_1.getPlanDetail)(root, planId);
|
|
114
|
+
if (!moved) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
reason: 'fs-error',
|
|
118
|
+
message: `Plan ${planId} was moved but could not be re-read.`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { ok: true, plan: moved };
|
|
122
|
+
};
|
|
123
|
+
exports.archivePlan = archivePlan;
|
|
124
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/serve/archive.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;GAgBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,uCAAyB;AACzB,2CAA6B;AAC7B,uDAA8D;AAe9D;;;;;;;;;GASG;AACI,MAAM,WAAW,GAAG,KAAK,EAAE,IAAY,EAAE,MAAc,EAA0B,EAAE;IACxF,MAAM,MAAM,GAAG,IAAA,+BAAa,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,QAAQ,MAAM,iBAAiB;SACzC,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,QAAQ,MAAM,uBAAuB;SAC/C,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,QAAQ,MAAM,mDAAmD;SAC3E,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IACvB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAErD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,oBAAoB;YAC5B,OAAO,EAAE,QAAQ,MAAM,0EAA0E;SAClG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,2EAA2E;QAC3E,yDAAyD;QACzD,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,QAAQ,MAAM,mDAAmD;SAC3E,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,MAAM,KAAK,GAAG,IAAA,+BAAa,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,QAAQ,MAAM,sCAAsC;SAC9D,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC,CAAC;AA7DW,QAAA,WAAW,eA6DtB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-plan derivation layer for the serve data layer.
|
|
3
|
+
*
|
|
4
|
+
* Scans a plan directory's `tasks/*.md` into structured task records, computes
|
|
5
|
+
* the derived plan lifecycle state (`drafted` -> `ready` -> `doing` -> `done`)
|
|
6
|
+
* with done/total counts, and resolves execution phases (from a blueprint
|
|
7
|
+
* document when present, otherwise inferred from task `group` + `dependencies`).
|
|
8
|
+
*
|
|
9
|
+
* Everything is synchronous and read-only: file reads only, no watches, no
|
|
10
|
+
* network, no writes.
|
|
11
|
+
*/
|
|
12
|
+
import { type MarkdownSection } from './markdown';
|
|
13
|
+
/** A single task parsed from a plan's `tasks/` directory. */
|
|
14
|
+
export interface Task {
|
|
15
|
+
id: number | undefined;
|
|
16
|
+
/** Task title (first `# ` heading), falling back to the filename. */
|
|
17
|
+
name: string;
|
|
18
|
+
group: string | undefined;
|
|
19
|
+
dependencies: number[];
|
|
20
|
+
status: string | undefined;
|
|
21
|
+
skills: string[];
|
|
22
|
+
/** Source filename (basename). */
|
|
23
|
+
file: string;
|
|
24
|
+
/** Raw markdown body after frontmatter. */
|
|
25
|
+
body: string;
|
|
26
|
+
/** Ordered named `##` sections of the body (via `sectionBody`, mirrors
|
|
27
|
+
* `PlanDetail.sections`). Additive to `body`, which is retained. */
|
|
28
|
+
sections: MarkdownSection[];
|
|
29
|
+
}
|
|
30
|
+
/** Derived lifecycle state of a plan. */
|
|
31
|
+
export type PlanState = 'drafted' | 'ready' | 'doing' | 'done';
|
|
32
|
+
/** Derived state plus done/total counts. */
|
|
33
|
+
export interface DerivedState {
|
|
34
|
+
state: PlanState;
|
|
35
|
+
done: number;
|
|
36
|
+
total: number;
|
|
37
|
+
}
|
|
38
|
+
/** An execution phase: an ordered group of task ids that run together. */
|
|
39
|
+
export interface Phase {
|
|
40
|
+
/** 1-based phase index. */
|
|
41
|
+
index: number;
|
|
42
|
+
/** Optional descriptive name (from a blueprint doc). */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** Task ids scheduled in this phase. */
|
|
45
|
+
taskIds: number[];
|
|
46
|
+
/** True when the phase holds more than one task. */
|
|
47
|
+
parallel: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Reads a plan directory's `tasks/*.md` into structured records. Missing dir -> []. */
|
|
50
|
+
export declare const scanTasks: (planDir: string) => Task[];
|
|
51
|
+
/**
|
|
52
|
+
* Computes the derived plan state and counts. Never throws on unknown statuses.
|
|
53
|
+
*
|
|
54
|
+
* - empty list -> `drafted`
|
|
55
|
+
* - tasks present, none started or done -> `ready`
|
|
56
|
+
* - all done -> `done`
|
|
57
|
+
* - otherwise -> `doing`
|
|
58
|
+
*/
|
|
59
|
+
export declare const deriveState: (tasks: Task[]) => DerivedState;
|
|
60
|
+
/**
|
|
61
|
+
* Parses phases from an "## Execution Blueprint" section if the plan body
|
|
62
|
+
* contains one. Returns undefined when no blueprint section is present.
|
|
63
|
+
*/
|
|
64
|
+
export declare const parseBlueprintPhases: (planBody: string) => Phase[] | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Infers phases from tasks by grouping on satisfied dependencies. Tasks whose
|
|
67
|
+
* dependencies are all already emitted (or absent from the task set) form a
|
|
68
|
+
* phase; dependents fall into later phases. Best-effort and cycle-safe: if no
|
|
69
|
+
* progress is made, the remaining tasks are emitted as a final phase rather
|
|
70
|
+
* than looping forever. Never throws.
|
|
71
|
+
*/
|
|
72
|
+
export declare const inferPhases: (tasks: Task[]) => Phase[];
|
|
73
|
+
/**
|
|
74
|
+
* Resolves a plan's phases: parses a blueprint document's phase list when the
|
|
75
|
+
* plan body contains one, otherwise infers phases from task dependencies.
|
|
76
|
+
*/
|
|
77
|
+
export declare const resolvePhases: (planBody: string, tasks: Task[]) => Phase[];
|
|
78
|
+
//# sourceMappingURL=derivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivation.d.ts","sourceRoot":"","sources":["../../src/serve/derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,6DAA6D;AAC7D,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb;wEACoE;IACpE,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,yCAAyC;AACzC,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/D,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,KAAK;IACpB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAKD,wFAAwF;AACxF,eAAO,MAAM,SAAS,GAAI,SAAS,MAAM,KAAG,IAAI,EAuC/C,CAAC;AASF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,IAAI,EAAE,KAAG,YA4B3C,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU,MAAM,KAAG,KAAK,EAAE,GAAG,SA6CjE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,IAAI,EAAE,KAAG,KAAK,EA0BhD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,KAAG,KAAK,EAIpE,CAAC"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-plan derivation layer for the serve data layer.
|
|
4
|
+
*
|
|
5
|
+
* Scans a plan directory's `tasks/*.md` into structured task records, computes
|
|
6
|
+
* the derived plan lifecycle state (`drafted` -> `ready` -> `doing` -> `done`)
|
|
7
|
+
* with done/total counts, and resolves execution phases (from a blueprint
|
|
8
|
+
* document when present, otherwise inferred from task `group` + `dependencies`).
|
|
9
|
+
*
|
|
10
|
+
* Everything is synchronous and read-only: file reads only, no watches, no
|
|
11
|
+
* network, no writes.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.resolvePhases = exports.inferPhases = exports.parseBlueprintPhases = exports.deriveState = exports.scanTasks = void 0;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const markdown_1 = require("./markdown");
|
|
51
|
+
const STATUS_DONE = 'completed';
|
|
52
|
+
const STATUS_NOT_STARTED = 'pending';
|
|
53
|
+
/** Reads a plan directory's `tasks/*.md` into structured records. Missing dir -> []. */
|
|
54
|
+
const scanTasks = (planDir) => {
|
|
55
|
+
const tasksDir = path.join(planDir, 'tasks');
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = fs.readdirSync(tasksDir, { withFileTypes: true });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return entries
|
|
64
|
+
.filter(e => e.isFile() && e.name.endsWith('.md'))
|
|
65
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
66
|
+
.flatMap(e => {
|
|
67
|
+
const filePath = path.join(tasksDir, e.name);
|
|
68
|
+
let content;
|
|
69
|
+
try {
|
|
70
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const fm = (0, markdown_1.parseFrontmatter)(content);
|
|
76
|
+
const body = (0, markdown_1.extractBody)(content);
|
|
77
|
+
const dependencies = fm.dependencies
|
|
78
|
+
.map(d => (typeof d === 'number' ? d : parseInt(d, 10)))
|
|
79
|
+
.filter((d) => !Number.isNaN(d));
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
id: fm.id,
|
|
83
|
+
name: (0, markdown_1.extractTitle)(body) ?? e.name.replace(/\.md$/, ''),
|
|
84
|
+
group: fm.group,
|
|
85
|
+
dependencies,
|
|
86
|
+
status: fm.status,
|
|
87
|
+
skills: fm.skills,
|
|
88
|
+
file: e.name,
|
|
89
|
+
body,
|
|
90
|
+
sections: (0, markdown_1.sectionBody)(body).sections,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
exports.scanTasks = scanTasks;
|
|
96
|
+
/** Classifies a task status. Unknown/in-progress values count as "started". */
|
|
97
|
+
const classify = (status) => {
|
|
98
|
+
if (status === STATUS_DONE)
|
|
99
|
+
return 'done';
|
|
100
|
+
if (status === STATUS_NOT_STARTED)
|
|
101
|
+
return 'notStarted';
|
|
102
|
+
return 'started';
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Computes the derived plan state and counts. Never throws on unknown statuses.
|
|
106
|
+
*
|
|
107
|
+
* - empty list -> `drafted`
|
|
108
|
+
* - tasks present, none started or done -> `ready`
|
|
109
|
+
* - all done -> `done`
|
|
110
|
+
* - otherwise -> `doing`
|
|
111
|
+
*/
|
|
112
|
+
const deriveState = (tasks) => {
|
|
113
|
+
const total = tasks.length;
|
|
114
|
+
if (total === 0) {
|
|
115
|
+
return { state: 'drafted', done: 0, total: 0 };
|
|
116
|
+
}
|
|
117
|
+
let done = 0;
|
|
118
|
+
let started = 0;
|
|
119
|
+
for (const task of tasks) {
|
|
120
|
+
const klass = classify(task.status);
|
|
121
|
+
if (klass === 'done') {
|
|
122
|
+
done += 1;
|
|
123
|
+
started += 1;
|
|
124
|
+
}
|
|
125
|
+
else if (klass === 'started') {
|
|
126
|
+
started += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let state;
|
|
130
|
+
if (done === total) {
|
|
131
|
+
state = 'done';
|
|
132
|
+
}
|
|
133
|
+
else if (started === 0) {
|
|
134
|
+
state = 'ready';
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
state = 'doing';
|
|
138
|
+
}
|
|
139
|
+
return { state, done, total };
|
|
140
|
+
};
|
|
141
|
+
exports.deriveState = deriveState;
|
|
142
|
+
const BLUEPRINT_SECTION_RE = /^##[ \t]+Execution Blueprint[ \t]*$/m;
|
|
143
|
+
const PHASE_HEADING_RE = /^###[ \t]+(?:✅[ \t]*)?Phase[ \t]+(\d+)[ \t]*:?[ \t]*(.*?)[ \t]*$/gm;
|
|
144
|
+
const TASK_REF_RE = /Task[ \t]+0*(\d+)/i;
|
|
145
|
+
/**
|
|
146
|
+
* Parses phases from an "## Execution Blueprint" section if the plan body
|
|
147
|
+
* contains one. Returns undefined when no blueprint section is present.
|
|
148
|
+
*/
|
|
149
|
+
const parseBlueprintPhases = (planBody) => {
|
|
150
|
+
const sectionMatch = planBody.match(BLUEPRINT_SECTION_RE);
|
|
151
|
+
if (!sectionMatch || sectionMatch.index === undefined)
|
|
152
|
+
return undefined;
|
|
153
|
+
const blueprint = planBody.slice(sectionMatch.index);
|
|
154
|
+
const headings = [];
|
|
155
|
+
PHASE_HEADING_RE.lastIndex = 0;
|
|
156
|
+
let m;
|
|
157
|
+
while ((m = PHASE_HEADING_RE.exec(blueprint)) !== null) {
|
|
158
|
+
headings.push({
|
|
159
|
+
index: m.index,
|
|
160
|
+
afterHeading: m.index + m[0].length,
|
|
161
|
+
name: (m[2] ?? '').trim(),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (headings.length === 0)
|
|
165
|
+
return undefined;
|
|
166
|
+
const phases = [];
|
|
167
|
+
for (let i = 0; i < headings.length; i++) {
|
|
168
|
+
const current = headings[i];
|
|
169
|
+
const next = headings[i + 1];
|
|
170
|
+
const end = next ? next.index : blueprint.length;
|
|
171
|
+
const segment = blueprint.slice(current.afterHeading, end);
|
|
172
|
+
const taskIds = [];
|
|
173
|
+
for (const line of segment.split(/\r?\n/)) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
// Only consider bulleted task references to avoid matching prose.
|
|
176
|
+
if (!trimmed.startsWith('-') && !trimmed.startsWith('*'))
|
|
177
|
+
continue;
|
|
178
|
+
const ref = trimmed.match(TASK_REF_RE);
|
|
179
|
+
if (ref && ref[1] !== undefined) {
|
|
180
|
+
const id = parseInt(ref[1], 10);
|
|
181
|
+
if (!Number.isNaN(id) && !taskIds.includes(id))
|
|
182
|
+
taskIds.push(id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
phases.push({
|
|
186
|
+
index: i + 1,
|
|
187
|
+
name: current.name.length > 0 ? current.name : undefined,
|
|
188
|
+
taskIds,
|
|
189
|
+
parallel: taskIds.length > 1,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return phases;
|
|
193
|
+
};
|
|
194
|
+
exports.parseBlueprintPhases = parseBlueprintPhases;
|
|
195
|
+
/**
|
|
196
|
+
* Infers phases from tasks by grouping on satisfied dependencies. Tasks whose
|
|
197
|
+
* dependencies are all already emitted (or absent from the task set) form a
|
|
198
|
+
* phase; dependents fall into later phases. Best-effort and cycle-safe: if no
|
|
199
|
+
* progress is made, the remaining tasks are emitted as a final phase rather
|
|
200
|
+
* than looping forever. Never throws.
|
|
201
|
+
*/
|
|
202
|
+
const inferPhases = (tasks) => {
|
|
203
|
+
const withIds = tasks.filter((t) => typeof t.id === 'number');
|
|
204
|
+
if (withIds.length === 0)
|
|
205
|
+
return [];
|
|
206
|
+
const idSet = new Set(withIds.map(t => t.id));
|
|
207
|
+
const emitted = new Set();
|
|
208
|
+
const phases = [];
|
|
209
|
+
let remaining = [...withIds];
|
|
210
|
+
while (remaining.length > 0) {
|
|
211
|
+
const ready = remaining.filter(t => t.dependencies.every(dep => !idSet.has(dep) || emitted.has(dep)));
|
|
212
|
+
const batch = ready.length > 0 ? ready : remaining; // no progress -> emit the rest
|
|
213
|
+
const taskIds = batch.map(t => t.id);
|
|
214
|
+
phases.push({
|
|
215
|
+
index: phases.length + 1,
|
|
216
|
+
taskIds,
|
|
217
|
+
parallel: taskIds.length > 1,
|
|
218
|
+
});
|
|
219
|
+
for (const id of taskIds)
|
|
220
|
+
emitted.add(id);
|
|
221
|
+
remaining = remaining.filter(t => !emitted.has(t.id));
|
|
222
|
+
}
|
|
223
|
+
return phases;
|
|
224
|
+
};
|
|
225
|
+
exports.inferPhases = inferPhases;
|
|
226
|
+
/**
|
|
227
|
+
* Resolves a plan's phases: parses a blueprint document's phase list when the
|
|
228
|
+
* plan body contains one, otherwise infers phases from task dependencies.
|
|
229
|
+
*/
|
|
230
|
+
const resolvePhases = (planBody, tasks) => {
|
|
231
|
+
const fromBlueprint = (0, exports.parseBlueprintPhases)(planBody);
|
|
232
|
+
if (fromBlueprint && fromBlueprint.length > 0)
|
|
233
|
+
return fromBlueprint;
|
|
234
|
+
return (0, exports.inferPhases)(tasks);
|
|
235
|
+
};
|
|
236
|
+
exports.resolvePhases = resolvePhases;
|
|
237
|
+
//# sourceMappingURL=derivation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derivation.js","sourceRoot":"","sources":["../../src/serve/derivation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Sent Events change stream for `npx strikethroo serve`.
|
|
3
|
+
*
|
|
4
|
+
* Provides `GET /api/events`: a `text/event-stream` that broadcasts a single
|
|
5
|
+
* coalesced `changed` ping whenever the workspace mutates on disk. The event
|
|
6
|
+
* carries no diff payload — clients refetch the affected API data (the browser
|
|
7
|
+
* consumer is a later plan).
|
|
8
|
+
*
|
|
9
|
+
* Exposes an {@link EventsHub} that owns the connected-client set and the
|
|
10
|
+
* workspace watcher, plus an `ApiHandler` the server registers so this module
|
|
11
|
+
* stays decoupled from routing. Connections are cleaned up on disconnect so no
|
|
12
|
+
* listeners or sockets leak. Node built-ins only.
|
|
13
|
+
*/
|
|
14
|
+
import * as http from 'http';
|
|
15
|
+
/**
|
|
16
|
+
* Owns the SSE client set and the workspace watcher. One hub per server. Start
|
|
17
|
+
* the watcher with {@link start} after the server is listening; release it with
|
|
18
|
+
* {@link close} when the server closes.
|
|
19
|
+
*/
|
|
20
|
+
export declare class EventsHub {
|
|
21
|
+
private readonly workspaceDir;
|
|
22
|
+
private readonly debounceMs?;
|
|
23
|
+
private readonly clients;
|
|
24
|
+
private watcher;
|
|
25
|
+
constructor(workspaceDir: string, debounceMs?: number | undefined);
|
|
26
|
+
/** Begins watching the workspace; coalesced changes broadcast to all clients. */
|
|
27
|
+
start(): void;
|
|
28
|
+
/** Stops the watcher and ends every open client connection. */
|
|
29
|
+
close(): void;
|
|
30
|
+
/** Number of currently connected SSE clients (used by tests). */
|
|
31
|
+
get clientCount(): number;
|
|
32
|
+
/** Sends a single coalesced `changed` event to every connected client. */
|
|
33
|
+
private broadcast;
|
|
34
|
+
/** Attaches an SSE response: writes headers, registers cleanup on close. */
|
|
35
|
+
handleConnection(req: http.IncomingMessage, res: http.ServerResponse): void;
|
|
36
|
+
/** An {@link ApiHandler} for `GET /api/events`; returns false otherwise. */
|
|
37
|
+
apiHandler: (req: http.IncomingMessage, res: http.ServerResponse, ctx: {
|
|
38
|
+
pathname: string;
|
|
39
|
+
}) => boolean;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/serve/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAM7B;;;;GAIG;AACH,qBAAa,SAAS;IAKlB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAL9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,OAAO,CAAiC;gBAG7B,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,YAAA;IAGtC,iFAAiF;IACjF,KAAK,IAAI,IAAI;IASb,+DAA+D;IAC/D,KAAK,IAAI,IAAI;IAWb,iEAAiE;IACjE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,0EAA0E;IAC1E,OAAO,CAAC,SAAS;IAMjB,4EAA4E;IAC5E,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI;IAyB3E,4EAA4E;IAC5E,UAAU,GACR,KAAK,IAAI,CAAC,eAAe,EACzB,KAAK,IAAI,CAAC,cAAc,EACxB,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KACxB,OAAO,CAMR;CACH"}
|