opsx-ui 0.1.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 +86 -0
- package/dist/cli/index.js +69 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/archive-source.js +2 -0
- package/dist/core/archive-source.js.map +1 -0
- package/dist/core/cli-openspec-source.js +75 -0
- package/dist/core/cli-openspec-source.js.map +1 -0
- package/dist/core/cli-runner.js +29 -0
- package/dist/core/cli-runner.js.map +1 -0
- package/dist/core/errors.js +30 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/filesystem-source.js +107 -0
- package/dist/core/filesystem-source.js.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/mappers.js +129 -0
- package/dist/core/mappers.js.map +1 -0
- package/dist/core/openspec-source.js +2 -0
- package/dist/core/openspec-source.js.map +1 -0
- package/dist/core/snapshot-service.js +36 -0
- package/dist/core/snapshot-service.js.map +1 -0
- package/dist/server/project-resolver.js +66 -0
- package/dist/server/project-resolver.js.map +1 -0
- package/dist/server/session.js +75 -0
- package/dist/server/session.js.map +1 -0
- package/dist/server/viewer-server.js +207 -0
- package/dist/server/viewer-server.js.map +1 -0
- package/dist/server/watcher.js +119 -0
- package/dist/server/watcher.js.map +1 -0
- package/dist/shared/contracts.js +7 -0
- package/dist/shared/contracts.js.map +1 -0
- package/dist/web/assets/index-BMLiBs2s.js +252 -0
- package/dist/web/assets/index-CVejAWk_.css +1 -0
- package/dist/web/index.html +32 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# opsx-ui
|
|
2
|
+
|
|
3
|
+
A read-only, live-updating **web viewer for everything OpenSpec touches** — a
|
|
4
|
+
browser home for your specs, changes, and proposed deltas that replaces the
|
|
5
|
+
`openspec view` TUI and updates _as agents work_.
|
|
6
|
+
|
|
7
|
+
> opsx-ui only **reads** OpenSpec state. It never writes to, or mutates, any
|
|
8
|
+
> project's `openspec/` directory. Driving agents (propose / apply / archive) is
|
|
9
|
+
> intentionally out of scope for now.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g opsx-ui
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires the [`openspec`](https://github.com/Fission-AI/OpenSpec) CLI **>= 1.5.0**
|
|
18
|
+
on your PATH.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
opsx-ui # open the OpenSpec project in the current directory
|
|
24
|
+
opsx-ui ./some/project # open a specific project
|
|
25
|
+
opsx-ui -g # global mode: skip cwd, show the project picker
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
|
|
30
|
+
| Flag | Meaning |
|
|
31
|
+
| ---------------- | -------------------------------------------------------- |
|
|
32
|
+
| `-g, --global` | Skip the current directory and open the project picker. |
|
|
33
|
+
| `-p, --port <n>` | Preferred port (auto-increments if taken). Default 4573. |
|
|
34
|
+
| `--poll` | Use filesystem polling (WSL / network mounts). |
|
|
35
|
+
| `--no-open` | Don't open a browser automatically. |
|
|
36
|
+
|
|
37
|
+
If the current directory has no `openspec/`, opsx-ui offers to point you at
|
|
38
|
+
another project or a registered store. Running it in a project with `openspec/`
|
|
39
|
+
opens straight to the **change board**.
|
|
40
|
+
|
|
41
|
+
## What you get
|
|
42
|
+
|
|
43
|
+
- **Change board** — every active change as a card with task progress and
|
|
44
|
+
validation health, updating live.
|
|
45
|
+
- **Activity feed** — a reverse-chronological rail of what just changed on disk,
|
|
46
|
+
so you can watch an agent's edits land in real time.
|
|
47
|
+
- **Spec browser** — drill into capabilities, requirements, and scenarios.
|
|
48
|
+
- **Spec diff** — a change's proposed deltas beside the current spec, grouped and
|
|
49
|
+
colored by operation (ADDED / MODIFIED / REMOVED).
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
A global CLI boots a local Node server (Express + WebSocket) that reads OpenSpec
|
|
54
|
+
state and streams it to a React/Vite UI.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Browser (React/Vite) ◀── WebSocket (live snapshot + activity) ──▶ Node server
|
|
58
|
+
│
|
|
59
|
+
chokidar watch(openspec/) │
|
|
60
|
+
▼
|
|
61
|
+
OpenSpecSource (port)
|
|
62
|
+
└ CliOpenSpecSource → `openspec … --json`
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
All OpenSpec reads go through a single `OpenSpecSource` interface that returns
|
|
66
|
+
typed view-models — the UI never sees raw CLI output. The CLI adapter is the one
|
|
67
|
+
swappable seam, so a future OpenSpec library or HTTP API drops in without
|
|
68
|
+
touching the server or UI.
|
|
69
|
+
|
|
70
|
+
## Develop
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run dev:server # local API + watcher (port 4573)
|
|
75
|
+
npm run dev:web # Vite dev server (port 5273, proxies /api and /ws)
|
|
76
|
+
npm test # unit tests (mappers, source port, read-only guarantee)
|
|
77
|
+
npm run build # bundle web + compile server into dist/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Scope
|
|
81
|
+
|
|
82
|
+
v1 renders the diff as operation-grouped delta cards beside the current spec
|
|
83
|
+
(no token-level inline highlighting) because OpenSpec's delta JSON does not
|
|
84
|
+
expose the `### Requirement:` header used as identity. Paired, highlighted
|
|
85
|
+
before→after diffing is a planned follow-up and slots in behind the same
|
|
86
|
+
`OpenSpecSource` seam.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { ViewerServer } from "../server/viewer-server.js";
|
|
6
|
+
import { ProjectResolver } from "../server/project-resolver.js";
|
|
7
|
+
const DEFAULT_PORT = 4573;
|
|
8
|
+
async function main() {
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program
|
|
11
|
+
.name("opsx-ui")
|
|
12
|
+
.description("A read-only, live web viewer for everything OpenSpec touches.")
|
|
13
|
+
.argument("[dir]", "project directory to open (defaults to current)")
|
|
14
|
+
.option("-g, --global", "skip current directory; open the project picker")
|
|
15
|
+
.option("-p, --port <port>", "preferred port", String(DEFAULT_PORT))
|
|
16
|
+
.option("--poll", "use filesystem polling (WSL/network mounts)")
|
|
17
|
+
.option("--no-open", "do not open a browser automatically")
|
|
18
|
+
.action(async (dir, options) => {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const initialProject = resolveInitialProject(cwd, dir, options.global);
|
|
21
|
+
const server = new ViewerServer({
|
|
22
|
+
cwd,
|
|
23
|
+
initialProject,
|
|
24
|
+
usePolling: options.poll,
|
|
25
|
+
});
|
|
26
|
+
const port = await server.start(Number(options.port) || DEFAULT_PORT);
|
|
27
|
+
const url = `http://localhost:${port}`;
|
|
28
|
+
printBanner(url, initialProject, cwd, Boolean(options.global));
|
|
29
|
+
if (options.open)
|
|
30
|
+
await open(url).catch(() => undefined);
|
|
31
|
+
const shutdown = async () => {
|
|
32
|
+
await server.stop();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
};
|
|
35
|
+
process.on("SIGINT", shutdown);
|
|
36
|
+
process.on("SIGTERM", shutdown);
|
|
37
|
+
});
|
|
38
|
+
await program.parseAsync(process.argv);
|
|
39
|
+
}
|
|
40
|
+
/** Decide what (if anything) to open at launch (viewer-cli spec). */
|
|
41
|
+
function resolveInitialProject(cwd, dir, global) {
|
|
42
|
+
if (global)
|
|
43
|
+
return null; // `-g`: never auto-open; show the picker.
|
|
44
|
+
if (dir) {
|
|
45
|
+
const target = path.resolve(cwd, dir);
|
|
46
|
+
return ProjectResolver.hasOpenSpec(target)
|
|
47
|
+
? ProjectResolver.forDirectory(target, "picked")
|
|
48
|
+
: null;
|
|
49
|
+
}
|
|
50
|
+
return ProjectResolver.fromCwd(cwd); // null → UI offers init / pick.
|
|
51
|
+
}
|
|
52
|
+
function printBanner(url, project, cwd, global) {
|
|
53
|
+
console.log(`\n opsx-ui → ${url}`);
|
|
54
|
+
if (project) {
|
|
55
|
+
console.log(` project → ${project.name} (${project.root})`);
|
|
56
|
+
}
|
|
57
|
+
else if (global) {
|
|
58
|
+
console.log(` global mode: choose a project in the browser`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log(` no openspec/ in ${cwd} — initialize or pick one in the browser`);
|
|
62
|
+
}
|
|
63
|
+
console.log("");
|
|
64
|
+
}
|
|
65
|
+
main().catch((error) => {
|
|
66
|
+
console.error(error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-source.js","sourceRoot":"","sources":["../../src/core/archive-source.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mapChangeList, mapDeltas, mapSpec, mapSpecList, mapStatus, mapValidation, } from "./mappers.js";
|
|
2
|
+
import { OpenSpecCliError, UnsupportedCliError } from "./errors.js";
|
|
3
|
+
const MIN_CLI_VERSION = "1.5.0";
|
|
4
|
+
/**
|
|
5
|
+
* Read-only OpenSpecSource backed by the `openspec` CLI (design D2/D3).
|
|
6
|
+
* Composes an injected CliRunner with the pure mappers; contains no rendering
|
|
7
|
+
* or filesystem-mutating logic.
|
|
8
|
+
*/
|
|
9
|
+
export class CliOpenSpecSource {
|
|
10
|
+
runner;
|
|
11
|
+
options;
|
|
12
|
+
constructor(runner, options = {}) {
|
|
13
|
+
this.runner = runner;
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
/** Append `--store <id>` to store-scoped commands when a store is active. */
|
|
17
|
+
scoped(args) {
|
|
18
|
+
return this.options.storeId
|
|
19
|
+
? [...args, "--store", this.options.storeId]
|
|
20
|
+
: args;
|
|
21
|
+
}
|
|
22
|
+
async json(args, map) {
|
|
23
|
+
const stdout = await this.runner.run(args);
|
|
24
|
+
try {
|
|
25
|
+
return map(JSON.parse(stdout));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new OpenSpecCliError(`Could not parse JSON from openspec ${args.join(" ")}: ${error.message}`, args);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async version() {
|
|
32
|
+
const out = (await this.runner.run(["--version"])).trim();
|
|
33
|
+
const found = out.split(/\s+/).pop() ?? out;
|
|
34
|
+
if (compareSemver(found, MIN_CLI_VERSION) < 0) {
|
|
35
|
+
throw new UnsupportedCliError(found, MIN_CLI_VERSION);
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
listChanges() {
|
|
40
|
+
return this.json(this.scoped(["list", "--json"]), mapChangeList);
|
|
41
|
+
}
|
|
42
|
+
listSpecs() {
|
|
43
|
+
return this.json(this.scoped(["list", "--specs", "--json"]), mapSpecList);
|
|
44
|
+
}
|
|
45
|
+
getSpec(specId) {
|
|
46
|
+
return this.json(this.scoped(["show", specId, "--json", "--type", "spec"]), mapSpec);
|
|
47
|
+
}
|
|
48
|
+
getDeltas(changeId) {
|
|
49
|
+
return this.json(this.scoped(["show", changeId, "--json", "--deltas-only"]), mapDeltas);
|
|
50
|
+
}
|
|
51
|
+
getStatus(changeId) {
|
|
52
|
+
return this.json(this.scoped(["status", "--change", changeId, "--json"]), mapStatus);
|
|
53
|
+
}
|
|
54
|
+
validateChanges() {
|
|
55
|
+
return this.json(this.scoped(["validate", "--changes", "--json"]), mapValidation);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Minimal semver compare (major.minor.patch). Returns -1 / 0 / 1. */
|
|
59
|
+
export function compareSemver(a, b) {
|
|
60
|
+
const pa = a
|
|
61
|
+
.replace(/^v/, "")
|
|
62
|
+
.split(".")
|
|
63
|
+
.map((n) => parseInt(n, 10) || 0);
|
|
64
|
+
const pb = b
|
|
65
|
+
.replace(/^v/, "")
|
|
66
|
+
.split(".")
|
|
67
|
+
.map((n) => parseInt(n, 10) || 0);
|
|
68
|
+
for (let i = 0; i < 3; i += 1) {
|
|
69
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
70
|
+
if (diff !== 0)
|
|
71
|
+
return Math.sign(diff);
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=cli-openspec-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-openspec-source.js","sourceRoot":"","sources":["../../src/core/cli-openspec-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { OpenSpecCliError, OpenSpecNotFoundError } from "./errors.js";
|
|
3
|
+
/** Production runner: spawns the real `openspec` binary. */
|
|
4
|
+
export class ExecCliRunner {
|
|
5
|
+
cwd;
|
|
6
|
+
bin;
|
|
7
|
+
maxBuffer;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.cwd = options.cwd;
|
|
10
|
+
this.bin = options.bin ?? "openspec";
|
|
11
|
+
this.maxBuffer = options.maxBuffer ?? 32 * 1024 * 1024;
|
|
12
|
+
}
|
|
13
|
+
run(args) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
execFile(this.bin, [...args, "--no-color"], { cwd: this.cwd, maxBuffer: this.maxBuffer }, (error, stdout, stderr) => {
|
|
16
|
+
if (error) {
|
|
17
|
+
if (error.code === "ENOENT") {
|
|
18
|
+
reject(new OpenSpecNotFoundError());
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
reject(new OpenSpecCliError(`openspec ${args.join(" ")} failed: ${error.message}`, args, stderr));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
resolve(stdout);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=cli-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-runner.js","sourceRoot":"","sources":["../../src/core/cli-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAoBtE,4DAA4D;AAC5D,MAAM,OAAO,aAAa;IACP,GAAG,CAAS;IACZ,GAAG,CAAS;IACZ,SAAS,CAAS;IAEnC,YAAY,OAA6B;QACvC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;QACrC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;IACzD,CAAC;IAED,GAAG,CAAC,IAAc;QAChB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,QAAQ,CACN,IAAI,CAAC,GAAG,EACR,CAAC,GAAG,IAAI,EAAE,YAAY,CAAC,EACvB,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAC5C,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;gBACxB,IAAI,KAAK,EAAE,CAAC;oBACV,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;wBACvD,MAAM,CAAC,IAAI,qBAAqB,EAAE,CAAC,CAAC;wBACpC,OAAO;oBACT,CAAC;oBACD,MAAM,CACJ,IAAI,gBAAgB,CAClB,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,OAAO,EAAE,EACrD,IAAI,EACJ,MAAM,CACP,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Raised when the `openspec` executable cannot be found on the machine. */
|
|
2
|
+
export class OpenSpecNotFoundError extends Error {
|
|
3
|
+
constructor(message = "The `openspec` CLI was not found on PATH.") {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "OpenSpecNotFoundError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/** Raised when the installed `openspec` CLI is too old to be supported. */
|
|
9
|
+
export class UnsupportedCliError extends Error {
|
|
10
|
+
found;
|
|
11
|
+
required;
|
|
12
|
+
constructor(found, required) {
|
|
13
|
+
super(`Unsupported openspec CLI ${found}; opsx-ui requires >= ${required}.`);
|
|
14
|
+
this.found = found;
|
|
15
|
+
this.required = required;
|
|
16
|
+
this.name = "UnsupportedCliError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Raised when an `openspec` invocation exits non-zero or emits bad JSON. */
|
|
20
|
+
export class OpenSpecCliError extends Error {
|
|
21
|
+
args;
|
|
22
|
+
stderr;
|
|
23
|
+
constructor(message, args, stderr) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.args = args;
|
|
26
|
+
this.stderr = stderr;
|
|
27
|
+
this.name = "OpenSpecCliError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,GAAG,2CAA2C;QAC/D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAE1B;IACA;IAFlB,YACkB,KAAa,EACb,QAAgB;QAEhC,KAAK,CACH,4BAA4B,KAAK,yBAAyB,QAAQ,GAAG,CACtE,CAAC;QALc,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;QAKhC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAGvB;IACA;IAHlB,YACE,OAAe,EACC,IAAc,EACd,MAAe;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAU;QACd,WAAM,GAAN,MAAM,CAAS;QAG/B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { OpenSpecCliError } from "./errors.js";
|
|
4
|
+
const ARTIFACT_FILES = ["proposal", "design", "tasks"];
|
|
5
|
+
const DATE_PREFIX = /^(\d{4}-\d{2}-\d{2})-(.+)$/;
|
|
6
|
+
/**
|
|
7
|
+
* Reads OpenSpec state directly from disk for the things the CLI can't provide:
|
|
8
|
+
* raw markdown and the archive tree (design D1/D6). Strictly read-only, and it
|
|
9
|
+
* refuses paths that escape the project's `openspec/` directory.
|
|
10
|
+
*/
|
|
11
|
+
export class FilesystemSource {
|
|
12
|
+
openspecDir;
|
|
13
|
+
archiveDir;
|
|
14
|
+
constructor(projectRoot) {
|
|
15
|
+
this.openspecDir = path.join(projectRoot, "openspec");
|
|
16
|
+
this.archiveDir = path.join(this.openspecDir, "changes", "archive");
|
|
17
|
+
}
|
|
18
|
+
async listArchived() {
|
|
19
|
+
const dirs = await this.safeReaddir(this.archiveDir);
|
|
20
|
+
const summaries = [];
|
|
21
|
+
for (const entry of dirs) {
|
|
22
|
+
const full = path.join(this.archiveDir, entry);
|
|
23
|
+
const info = await stat(full).catch(() => null);
|
|
24
|
+
if (!info?.isDirectory())
|
|
25
|
+
continue;
|
|
26
|
+
const tasks = await this.readTaskProgress(path.join(full, "tasks.md"));
|
|
27
|
+
const match = DATE_PREFIX.exec(entry);
|
|
28
|
+
summaries.push({
|
|
29
|
+
id: entry,
|
|
30
|
+
name: match ? match[2] : entry,
|
|
31
|
+
archivedDate: match ? match[1] : null,
|
|
32
|
+
tasks,
|
|
33
|
+
mtime: info.mtimeMs,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return summaries
|
|
37
|
+
.sort((a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : b.mtime - a.mtime))
|
|
38
|
+
.map(({ mtime: _mtime, ...summary }) => summary);
|
|
39
|
+
}
|
|
40
|
+
async getArchivedChange(id) {
|
|
41
|
+
const dir = this.resolveArchiveEntry(id);
|
|
42
|
+
const [summary] = (await this.listArchived()).filter((s) => s.id === id);
|
|
43
|
+
if (!summary) {
|
|
44
|
+
throw new OpenSpecCliError(`Archived change not found: ${id}`, [
|
|
45
|
+
"archive",
|
|
46
|
+
id,
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
const artifacts = [];
|
|
50
|
+
for (const artifact of ARTIFACT_FILES) {
|
|
51
|
+
const content = await this.readIfExists(path.join(dir, `${artifact}.md`));
|
|
52
|
+
if (content !== null)
|
|
53
|
+
artifacts.push({ id: artifact, content });
|
|
54
|
+
}
|
|
55
|
+
const deltas = await this.readArchivedDeltas(path.join(dir, "specs"));
|
|
56
|
+
return { ...summary, artifacts, deltas };
|
|
57
|
+
}
|
|
58
|
+
async getRawDocument(relativePath) {
|
|
59
|
+
const resolved = this.resolveWithin(this.openspecDir, relativePath);
|
|
60
|
+
const content = await readFile(resolved, "utf8");
|
|
61
|
+
return { path: relativePath, content };
|
|
62
|
+
}
|
|
63
|
+
// --- helpers ---
|
|
64
|
+
async readArchivedDeltas(specsDir) {
|
|
65
|
+
const capabilities = await this.safeReaddir(specsDir);
|
|
66
|
+
const deltas = [];
|
|
67
|
+
for (const cap of capabilities) {
|
|
68
|
+
const content = await this.readIfExists(path.join(specsDir, cap, "spec.md"));
|
|
69
|
+
if (content !== null)
|
|
70
|
+
deltas.push({ spec: cap, content });
|
|
71
|
+
}
|
|
72
|
+
return deltas;
|
|
73
|
+
}
|
|
74
|
+
async readTaskProgress(tasksPath) {
|
|
75
|
+
const content = await this.readIfExists(tasksPath);
|
|
76
|
+
if (content === null)
|
|
77
|
+
return { completed: 0, total: 0 };
|
|
78
|
+
const done = (content.match(/^\s*- \[x\]/gim) ?? []).length;
|
|
79
|
+
const todo = (content.match(/^\s*- \[ \]/gim) ?? []).length;
|
|
80
|
+
return { completed: done, total: done + todo };
|
|
81
|
+
}
|
|
82
|
+
async safeReaddir(dir) {
|
|
83
|
+
return readdir(dir).catch(() => []);
|
|
84
|
+
}
|
|
85
|
+
async readIfExists(file) {
|
|
86
|
+
return readFile(file, "utf8").catch(() => null);
|
|
87
|
+
}
|
|
88
|
+
/** Resolve an archive entry id, guarding against traversal. */
|
|
89
|
+
resolveArchiveEntry(id) {
|
|
90
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..")) {
|
|
91
|
+
throw new OpenSpecCliError(`Invalid archive id: ${id}`, ["archive", id]);
|
|
92
|
+
}
|
|
93
|
+
return path.join(this.archiveDir, id);
|
|
94
|
+
}
|
|
95
|
+
/** Resolve `relativePath` and ensure it stays under `base`. */
|
|
96
|
+
resolveWithin(base, relativePath) {
|
|
97
|
+
const resolved = path.resolve(base, relativePath);
|
|
98
|
+
const rel = path.relative(base, resolved);
|
|
99
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
100
|
+
throw new OpenSpecCliError(`Path escapes openspec/: ${relativePath}`, [
|
|
101
|
+
relativePath,
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=filesystem-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem-source.js","sourceRoot":"","sources":["../../src/core/filesystem-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./errors.js";
|
|
2
|
+
export * from "./cli-runner.js";
|
|
3
|
+
export * from "./openspec-source.js";
|
|
4
|
+
export * from "./cli-openspec-source.js";
|
|
5
|
+
export * from "./archive-source.js";
|
|
6
|
+
export * from "./filesystem-source.js";
|
|
7
|
+
export * from "./snapshot-service.js";
|
|
8
|
+
export * as mappers from "./mappers.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
function asArray(value) {
|
|
2
|
+
return Array.isArray(value) ? value : [];
|
|
3
|
+
}
|
|
4
|
+
function asString(value, fallback = "") {
|
|
5
|
+
return typeof value === "string" ? value : fallback;
|
|
6
|
+
}
|
|
7
|
+
function asNumber(value, fallback = 0) {
|
|
8
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
9
|
+
}
|
|
10
|
+
function mapScenario(raw) {
|
|
11
|
+
const obj = (raw ?? {});
|
|
12
|
+
return { rawText: asString(obj.rawText) };
|
|
13
|
+
}
|
|
14
|
+
function mapRequirement(raw) {
|
|
15
|
+
const obj = (raw ?? {});
|
|
16
|
+
return {
|
|
17
|
+
name: typeof obj.name === "string" ? obj.name : null,
|
|
18
|
+
text: asString(obj.text),
|
|
19
|
+
scenarios: asArray(obj.scenarios).map(mapScenario),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function mapChangeList(raw) {
|
|
23
|
+
const obj = (raw ?? {});
|
|
24
|
+
return asArray(obj.changes).map((entry) => {
|
|
25
|
+
const c = (entry ?? {});
|
|
26
|
+
const completed = asNumber(c.completedTasks);
|
|
27
|
+
const total = asNumber(c.totalTasks);
|
|
28
|
+
return {
|
|
29
|
+
name: asString(c.name),
|
|
30
|
+
status: asString(c.status, "in-progress"),
|
|
31
|
+
tasks: { completed, total },
|
|
32
|
+
validation: "unknown",
|
|
33
|
+
lastModified: typeof c.lastModified === "string" ? c.lastModified : null,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function mapSpecList(raw) {
|
|
38
|
+
const obj = (raw ?? {});
|
|
39
|
+
return asArray(obj.specs).map((entry) => {
|
|
40
|
+
const s = (entry ?? {});
|
|
41
|
+
const id = asString(s.id);
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
title: asString(s.title, id),
|
|
45
|
+
requirementCount: asNumber(s.requirementCount),
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function mapSpec(raw) {
|
|
50
|
+
const obj = (raw ?? {});
|
|
51
|
+
const id = asString(obj.id);
|
|
52
|
+
return {
|
|
53
|
+
id,
|
|
54
|
+
title: asString(obj.title, id),
|
|
55
|
+
overview: typeof obj.overview === "string" ? obj.overview : null,
|
|
56
|
+
requirements: asArray(obj.requirements).map(mapRequirement),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const KNOWN_OPERATIONS = [
|
|
60
|
+
"ADDED",
|
|
61
|
+
"MODIFIED",
|
|
62
|
+
"REMOVED",
|
|
63
|
+
"RENAMED",
|
|
64
|
+
];
|
|
65
|
+
function mapOperation(value) {
|
|
66
|
+
const upper = asString(value).toUpperCase();
|
|
67
|
+
return (KNOWN_OPERATIONS.find((op) => op === upper) ??
|
|
68
|
+
"MODIFIED");
|
|
69
|
+
}
|
|
70
|
+
export function mapDeltas(raw) {
|
|
71
|
+
const obj = (raw ?? {});
|
|
72
|
+
const deltas = asArray(obj.deltas).map((entry) => {
|
|
73
|
+
const d = (entry ?? {});
|
|
74
|
+
return {
|
|
75
|
+
spec: asString(d.spec),
|
|
76
|
+
operation: mapOperation(d.operation),
|
|
77
|
+
description: asString(d.description),
|
|
78
|
+
requirement: d.requirement && typeof d.requirement === "object"
|
|
79
|
+
? mapRequirement(d.requirement)
|
|
80
|
+
: null,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
changeId: asString(obj.id),
|
|
85
|
+
title: asString(obj.title, asString(obj.id)),
|
|
86
|
+
deltaCount: asNumber(obj.deltaCount, deltas.length),
|
|
87
|
+
deltas,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function mapStatus(raw) {
|
|
91
|
+
const obj = (raw ?? {});
|
|
92
|
+
return {
|
|
93
|
+
changeName: asString(obj.changeName),
|
|
94
|
+
schema: asString(obj.schemaName),
|
|
95
|
+
isComplete: obj.isComplete === true,
|
|
96
|
+
applyRequires: asArray(obj.applyRequires).map((x) => asString(x)),
|
|
97
|
+
artifacts: asArray(obj.artifacts).map((entry) => {
|
|
98
|
+
const a = (entry ?? {});
|
|
99
|
+
return { id: asString(a.id), status: asString(a.status) };
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function mapIssue(raw) {
|
|
104
|
+
if (typeof raw === "string")
|
|
105
|
+
return { level: "error", message: raw };
|
|
106
|
+
const obj = (raw ?? {});
|
|
107
|
+
return {
|
|
108
|
+
level: asString(obj.level ?? obj.severity, "error"),
|
|
109
|
+
message: asString(obj.message ?? obj.text, "Validation issue"),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function mapValidation(raw) {
|
|
113
|
+
const obj = (raw ?? {});
|
|
114
|
+
return asArray(obj.items).map((entry) => {
|
|
115
|
+
const i = (entry ?? {});
|
|
116
|
+
return {
|
|
117
|
+
id: asString(i.id),
|
|
118
|
+
valid: i.valid === true,
|
|
119
|
+
issues: asArray(i.issues).map(mapIssue),
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Fold a validation result into the board status a card renders. */
|
|
124
|
+
export function toValidationStatus(view) {
|
|
125
|
+
if (!view)
|
|
126
|
+
return "unknown";
|
|
127
|
+
return view.valid ? "valid" : "invalid";
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=mappers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mappers.js","sourceRoot":"","sources":["../../src/core/mappers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openspec-source.js","sourceRoot":"","sources":["../../src/core/openspec-source.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { toValidationStatus } from "./mappers.js";
|
|
2
|
+
/**
|
|
3
|
+
* Composes the board-level snapshot from independent source calls (design
|
|
4
|
+
* D3/D1). Merges per-change validation health into each card and includes the
|
|
5
|
+
* archived-change list from the filesystem source. Pure orchestration — no CLI
|
|
6
|
+
* or filesystem knowledge of its own, so it is unit-testable against mocks.
|
|
7
|
+
*/
|
|
8
|
+
export class SnapshotService {
|
|
9
|
+
source;
|
|
10
|
+
archive;
|
|
11
|
+
constructor(source, archive) {
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.archive = archive;
|
|
14
|
+
}
|
|
15
|
+
async build(project) {
|
|
16
|
+
const [changes, specs, validations, archived] = await Promise.all([
|
|
17
|
+
this.source.listChanges(),
|
|
18
|
+
this.source.listSpecs(),
|
|
19
|
+
this.source.validateChanges().catch(() => []),
|
|
20
|
+
this.archive.listArchived().catch(() => []),
|
|
21
|
+
]);
|
|
22
|
+
const validationById = new Map(validations.map((v) => [v.id, v]));
|
|
23
|
+
const withHealth = changes.map((change) => ({
|
|
24
|
+
...change,
|
|
25
|
+
validation: toValidationStatus(validationById.get(change.name)),
|
|
26
|
+
}));
|
|
27
|
+
return {
|
|
28
|
+
project,
|
|
29
|
+
changes: withHealth,
|
|
30
|
+
specs,
|
|
31
|
+
archived,
|
|
32
|
+
generatedAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=snapshot-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-service.js","sourceRoot":"","sources":["../../src/core/snapshot-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IAEP;IACA;IAFnB,YACmB,MAAsB,EACtB,OAAsB;QADtB,WAAM,GAAN,MAAM,CAAgB;QACtB,YAAO,GAAP,OAAO,CAAe;IACtC,CAAC;IAEJ,KAAK,CAAC,KAAK,CAAC,OAAoB;QAC9B,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;YACzB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;YACvB,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;SAC5C,CAAC,CAAC;QAEH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC1C,GAAG,MAAM;YACT,UAAU,EAAE,kBAAkB,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SAChE,CAAC,CAAC,CAAC;QAEJ,OAAO;YACL,OAAO;YACP,OAAO,EAAE,UAAU;YACnB,KAAK;YACL,QAAQ;YACR,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACtC,CAAC;IACJ,CAAC;CACF"}
|