@workflow-cannon/workspace-kit 0.15.0 → 0.16.1
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 +55 -106
- package/dist/cli/doctor-planning-issues.d.ts +6 -0
- package/dist/cli/doctor-planning-issues.js +37 -0
- package/dist/cli.js +3 -0
- package/dist/core/config-metadata.js +56 -1
- package/dist/modules/approvals/review-runtime.js +3 -11
- package/dist/modules/improvement/generate-recommendations-runtime.js +3 -11
- package/dist/modules/task-engine/doctor-planning-persistence.d.ts +9 -0
- package/dist/modules/task-engine/doctor-planning-persistence.js +77 -0
- package/dist/modules/task-engine/index.d.ts +2 -0
- package/dist/modules/task-engine/index.js +44 -46
- package/dist/modules/task-engine/migrate-task-persistence-runtime.d.ts +2 -0
- package/dist/modules/task-engine/migrate-task-persistence-runtime.js +192 -0
- package/dist/modules/task-engine/planning-config.d.ts +10 -0
- package/dist/modules/task-engine/planning-config.js +37 -0
- package/dist/modules/task-engine/planning-open.d.ts +16 -0
- package/dist/modules/task-engine/planning-open.js +34 -0
- package/dist/modules/task-engine/sqlite-dual-planning.d.ts +21 -0
- package/dist/modules/task-engine/sqlite-dual-planning.js +137 -0
- package/dist/modules/task-engine/store.d.ts +12 -3
- package/dist/modules/task-engine/store.js +62 -38
- package/dist/modules/task-engine/wishlist-store.d.ts +12 -3
- package/dist/modules/task-engine/wishlist-store.js +62 -40
- package/package.json +11 -2
- package/src/modules/documentation/README.md +1 -0
- package/src/modules/documentation/instructions/document-project.md +1 -0
- package/src/modules/documentation/instructions/generate-document.md +1 -0
- package/src/modules/documentation/templates/README.md +89 -0
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { TaskEngineError } from "./transitions.js";
|
|
5
|
-
const
|
|
5
|
+
export const DEFAULT_TASK_STORE_PATH = ".workspace-kit/tasks/state.json";
|
|
6
6
|
function emptyStore() {
|
|
7
7
|
return {
|
|
8
8
|
schemaVersion: 1,
|
|
@@ -14,49 +14,73 @@ function emptyStore() {
|
|
|
14
14
|
}
|
|
15
15
|
export class TaskStore {
|
|
16
16
|
document;
|
|
17
|
-
|
|
18
|
-
constructor(
|
|
19
|
-
this.
|
|
17
|
+
persistence;
|
|
18
|
+
constructor(persistence) {
|
|
19
|
+
this.persistence = persistence;
|
|
20
20
|
this.document = emptyStore();
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
static forJsonFile(workspacePath, storeRelativePath) {
|
|
23
|
+
const filePath = path.resolve(workspacePath, storeRelativePath ?? DEFAULT_TASK_STORE_PATH);
|
|
24
|
+
return new TaskStore({
|
|
25
|
+
pathLabel: filePath,
|
|
26
|
+
loadDocument: async () => {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (parsed.schemaVersion !== 1) {
|
|
31
|
+
throw new TaskEngineError("storage-read-error", `Unsupported schema version: ${parsed.schemaVersion}`);
|
|
32
|
+
}
|
|
33
|
+
if (!Array.isArray(parsed.mutationLog)) {
|
|
34
|
+
parsed.mutationLog = [];
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err.code === "ENOENT") {
|
|
40
|
+
return emptyStore();
|
|
41
|
+
}
|
|
42
|
+
if (err instanceof TaskEngineError) {
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
throw new TaskEngineError("storage-read-error", `Failed to read task store: ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
saveDocument: async (doc) => {
|
|
49
|
+
const dir = path.dirname(filePath);
|
|
50
|
+
const tmpPath = `${filePath}.${crypto.randomUUID().slice(0, 8)}.tmp`;
|
|
51
|
+
try {
|
|
52
|
+
await fs.mkdir(dir, { recursive: true });
|
|
53
|
+
await fs.writeFile(tmpPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
|
|
54
|
+
await fs.rename(tmpPath, filePath);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
try {
|
|
58
|
+
await fs.unlink(tmpPath);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* cleanup best-effort */
|
|
62
|
+
}
|
|
63
|
+
throw new TaskEngineError("storage-write-error", `Failed to write task store: ${err.message}`);
|
|
64
|
+
}
|
|
32
65
|
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
static forSqliteDual(dual) {
|
|
69
|
+
return new TaskStore({
|
|
70
|
+
pathLabel: `${dual.getDisplayPath()}#task_engine`,
|
|
71
|
+
loadDocument: async () => dual.taskDocument,
|
|
72
|
+
saveDocument: async (doc) => {
|
|
73
|
+
dual.seedFromDocuments(doc, dual.wishlistDocument);
|
|
74
|
+
dual.persistSync();
|
|
38
75
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async load() {
|
|
79
|
+
this.document = await this.persistence.loadDocument();
|
|
43
80
|
}
|
|
44
81
|
async save() {
|
|
45
82
|
this.document.lastUpdated = new Date().toISOString();
|
|
46
|
-
|
|
47
|
-
const tmpPath = `${this.filePath}.${crypto.randomUUID().slice(0, 8)}.tmp`;
|
|
48
|
-
try {
|
|
49
|
-
await fs.mkdir(dir, { recursive: true });
|
|
50
|
-
await fs.writeFile(tmpPath, JSON.stringify(this.document, null, 2) + "\n", "utf8");
|
|
51
|
-
await fs.rename(tmpPath, this.filePath);
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
try {
|
|
55
|
-
await fs.unlink(tmpPath);
|
|
56
|
-
}
|
|
57
|
-
catch { /* cleanup best-effort */ }
|
|
58
|
-
throw new TaskEngineError("storage-write-error", `Failed to write task store: ${err.message}`);
|
|
59
|
-
}
|
|
83
|
+
await this.persistence.saveDocument(this.document);
|
|
60
84
|
}
|
|
61
85
|
getAllTasks() {
|
|
62
86
|
return [...this.document.tasks];
|
|
@@ -99,7 +123,7 @@ export class TaskStore {
|
|
|
99
123
|
this.document.tasks = tasks.map((t) => ({ ...t }));
|
|
100
124
|
}
|
|
101
125
|
getFilePath() {
|
|
102
|
-
return this.
|
|
126
|
+
return this.persistence.pathLabel;
|
|
103
127
|
}
|
|
104
128
|
getLastUpdated() {
|
|
105
129
|
return this.document.lastUpdated;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import type { WishlistItem } from "./wishlist-types.js";
|
|
1
|
+
import type { WishlistItem, WishlistStoreDocument } from "./wishlist-types.js";
|
|
2
|
+
import type { SqliteDualPlanningStore } from "./sqlite-dual-planning.js";
|
|
3
|
+
export declare const DEFAULT_WISHLIST_PATH = ".workspace-kit/wishlist/state.json";
|
|
4
|
+
export type WishlistStorePersistence = {
|
|
5
|
+
loadDocument: () => Promise<WishlistStoreDocument>;
|
|
6
|
+
saveDocument: (doc: WishlistStoreDocument) => Promise<void>;
|
|
7
|
+
pathLabel: string;
|
|
8
|
+
};
|
|
2
9
|
export declare class WishlistStore {
|
|
3
10
|
private document;
|
|
4
|
-
private readonly
|
|
5
|
-
constructor(
|
|
11
|
+
private readonly persistence;
|
|
12
|
+
constructor(persistence: WishlistStorePersistence);
|
|
13
|
+
static forJsonFile(workspacePath: string, storeRelativePath?: string): WishlistStore;
|
|
14
|
+
static forSqliteDual(dual: SqliteDualPlanningStore): WishlistStore;
|
|
6
15
|
load(): Promise<void>;
|
|
7
16
|
save(): Promise<void>;
|
|
8
17
|
getAllItems(): WishlistItem[];
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import { TaskEngineError } from "./transitions.js";
|
|
5
|
-
const DEFAULT_WISHLIST_PATH = ".workspace-kit/wishlist/state.json";
|
|
5
|
+
export const DEFAULT_WISHLIST_PATH = ".workspace-kit/wishlist/state.json";
|
|
6
6
|
function emptyWishlistDoc() {
|
|
7
7
|
return {
|
|
8
8
|
schemaVersion: 1,
|
|
@@ -12,51 +12,73 @@ function emptyWishlistDoc() {
|
|
|
12
12
|
}
|
|
13
13
|
export class WishlistStore {
|
|
14
14
|
document;
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
this.
|
|
15
|
+
persistence;
|
|
16
|
+
constructor(persistence) {
|
|
17
|
+
this.persistence = persistence;
|
|
18
18
|
this.document = emptyWishlistDoc();
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
static forJsonFile(workspacePath, storeRelativePath) {
|
|
21
|
+
const filePath = path.resolve(workspacePath, storeRelativePath ?? DEFAULT_WISHLIST_PATH);
|
|
22
|
+
return new WishlistStore({
|
|
23
|
+
pathLabel: filePath,
|
|
24
|
+
loadDocument: async () => {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (parsed.schemaVersion !== 1) {
|
|
29
|
+
throw new TaskEngineError("storage-read-error", `Unsupported wishlist schema version: ${parsed.schemaVersion}`);
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(parsed.items)) {
|
|
32
|
+
throw new TaskEngineError("storage-read-error", "Wishlist store 'items' must be an array");
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (err.code === "ENOENT") {
|
|
38
|
+
return emptyWishlistDoc();
|
|
39
|
+
}
|
|
40
|
+
if (err instanceof TaskEngineError) {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
throw new TaskEngineError("storage-read-error", `Failed to read wishlist store: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
saveDocument: async (doc) => {
|
|
47
|
+
const dir = path.dirname(filePath);
|
|
48
|
+
const tmpPath = `${filePath}.${crypto.randomUUID().slice(0, 8)}.tmp`;
|
|
49
|
+
try {
|
|
50
|
+
await fs.mkdir(dir, { recursive: true });
|
|
51
|
+
await fs.writeFile(tmpPath, JSON.stringify(doc, null, 2) + "\n", "utf8");
|
|
52
|
+
await fs.rename(tmpPath, filePath);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
try {
|
|
56
|
+
await fs.unlink(tmpPath);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* cleanup best-effort */
|
|
60
|
+
}
|
|
61
|
+
throw new TaskEngineError("storage-write-error", `Failed to write wishlist store: ${err.message}`);
|
|
62
|
+
}
|
|
29
63
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
static forSqliteDual(dual) {
|
|
67
|
+
return new WishlistStore({
|
|
68
|
+
pathLabel: `${dual.getDisplayPath()}#wishlist`,
|
|
69
|
+
loadDocument: async () => dual.wishlistDocument,
|
|
70
|
+
saveDocument: async (doc) => {
|
|
71
|
+
dual.seedFromDocuments(dual.taskDocument, doc);
|
|
72
|
+
dual.persistSync();
|
|
36
73
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async load() {
|
|
77
|
+
this.document = await this.persistence.loadDocument();
|
|
41
78
|
}
|
|
42
79
|
async save() {
|
|
43
80
|
this.document.lastUpdated = new Date().toISOString();
|
|
44
|
-
|
|
45
|
-
const tmpPath = `${this.filePath}.${crypto.randomUUID().slice(0, 8)}.tmp`;
|
|
46
|
-
try {
|
|
47
|
-
await fs.mkdir(dir, { recursive: true });
|
|
48
|
-
await fs.writeFile(tmpPath, JSON.stringify(this.document, null, 2) + "\n", "utf8");
|
|
49
|
-
await fs.rename(tmpPath, this.filePath);
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
try {
|
|
53
|
-
await fs.unlink(tmpPath);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
/* cleanup best-effort */
|
|
57
|
-
}
|
|
58
|
-
throw new TaskEngineError("storage-write-error", `Failed to write wishlist store: ${err.message}`);
|
|
59
|
-
}
|
|
81
|
+
await this.persistence.saveDocument(this.document);
|
|
60
82
|
}
|
|
61
83
|
getAllItems() {
|
|
62
84
|
return [...this.document.items];
|
|
@@ -78,7 +100,7 @@ export class WishlistStore {
|
|
|
78
100
|
this.document.items[idx] = { ...item };
|
|
79
101
|
}
|
|
80
102
|
getFilePath() {
|
|
81
|
-
return this.
|
|
103
|
+
return this.persistence.pathLabel;
|
|
82
104
|
}
|
|
83
105
|
getLastUpdated() {
|
|
84
106
|
return this.document.lastUpdated;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow-cannon/workspace-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"packageManager": "pnpm@10.0.0",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"ui:watch": "cd extensions/cursor-workflow-cannon && npm run watch"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
44
45
|
"@types/node": "^25.5.0",
|
|
45
46
|
"typescript": "^5.9.3"
|
|
46
47
|
},
|
|
@@ -48,5 +49,13 @@
|
|
|
48
49
|
"dist",
|
|
49
50
|
"src/modules/documentation",
|
|
50
51
|
"package.json"
|
|
51
|
-
]
|
|
52
|
+
],
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"better-sqlite3": "^12.8.0"
|
|
55
|
+
},
|
|
56
|
+
"pnpm": {
|
|
57
|
+
"onlyBuiltDependencies": [
|
|
58
|
+
"better-sqlite3"
|
|
59
|
+
]
|
|
60
|
+
}
|
|
52
61
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Human output path: docs/maintainers/README.md (image uses ../title_image.png from that location).
|
|
3
|
+
Root README.md: mirror this body but use title_image.png and keep the agent notice line at the very top.
|
|
4
|
+
Audience: developers cloning the repo or adding @workflow-cannon/workspace-kit to a project who want to run something useful in minutes.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<img src="../title_image.png" alt="Workflow Cannon" width="720" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
# Workflow Cannon
|
|
12
|
+
|
|
13
|
+
**[`@workflow-cannon/workspace-kit`](https://www.npmjs.com/package/@workflow-cannon/workspace-kit)** — CLI, task engine, and workflow contracts for repos that want deterministic, policy-governed automation with clear evidence.
|
|
14
|
+
|
|
15
|
+
## Quick start (clone this repo)
|
|
16
|
+
|
|
17
|
+
**Needs:** Node.js **22+** (see CI), **pnpm 10** (see `packageManager` in `package.json`).
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/NJLaPrell/workflow-cannon.git
|
|
21
|
+
cd workflow-cannon
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Verify the kit sees your workspace:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node dist/cli.js doctor
|
|
30
|
+
node dist/cli.js --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Try **read-only** task-engine queries:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node dist/cli.js run list-tasks '{}'
|
|
37
|
+
node dist/cli.js run get-next-actions '{}'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Developing:** after edits, `pnpm run build` then `pnpm test` (or `pnpm run phase5-gates` before larger changes). If `workspace-kit` is not on your `PATH`, use `node dist/cli.js …` from the repo root (same as above).
|
|
41
|
+
|
|
42
|
+
## Quick start (use the package in another project)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @workflow-cannon/workspace-kit
|
|
46
|
+
npx workspace-kit --help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or with pnpm: `pnpm add @workflow-cannon/workspace-kit` then `pnpm exec workspace-kit --help`.
|
|
50
|
+
|
|
51
|
+
## What this repo contains
|
|
52
|
+
|
|
53
|
+
| Area | What |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| **CLI** | `workspace-kit` — `doctor`, `config`, `run <module-command>` (see `workspace-kit run` with no args for the list). |
|
|
56
|
+
| **Task engine** | Canonical queue in `.workspace-kit/tasks/state.json`; lifecycle via `run-transition`. Wishlist ideation uses ids `W###` (see maintainer runbooks). |
|
|
57
|
+
| **Docs** | Maintainer process, roadmap, and changelog under `docs/maintainers/`. |
|
|
58
|
+
| **Cursor extension** (optional) | Thin UI in `extensions/cursor-workflow-cannon/` — build with `pnpm run ui:prepare`. |
|
|
59
|
+
|
|
60
|
+
There is **no** built-in IDE slash command like `/qt` from this package; editor integrations are **your** config (e.g. `.cursor/commands/`), while **`workspace-kit`** is the supported CLI.
|
|
61
|
+
|
|
62
|
+
## Policy and approvals (read this before mutating state)
|
|
63
|
+
|
|
64
|
+
Sensitive `workspace-kit run` commands require JSON **`policyApproval`** in the third CLI argument. Chat approval is not enough. Env-based approval applies to `init` / `upgrade` / `config`, not the `run` path.
|
|
65
|
+
|
|
66
|
+
- **Human guide:** [`docs/maintainers/POLICY-APPROVAL.md`](POLICY-APPROVAL.md)
|
|
67
|
+
- **Copy-paste table:** [`docs/maintainers/AGENT-CLI-MAP.md`](AGENT-CLI-MAP.md)
|
|
68
|
+
|
|
69
|
+
## Project status and roadmap
|
|
70
|
+
|
|
71
|
+
Release cadence, phase history, and strategic decisions: [`docs/maintainers/ROADMAP.md`](ROADMAP.md). **Live execution queue:** `.workspace-kit/tasks/state.json` (`status` and `id` are authoritative — not this README’s milestone bullets).
|
|
72
|
+
|
|
73
|
+
Snapshot: [`docs/maintainers/data/workspace-kit-status.yaml`](data/workspace-kit-status.yaml).
|
|
74
|
+
|
|
75
|
+
## Where to go next
|
|
76
|
+
|
|
77
|
+
| Goal | Start here |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| Goals, trade-offs, gates | [`.ai/PRINCIPLES.md`](../.ai/PRINCIPLES.md) |
|
|
80
|
+
| Roadmap & versions | [`ROADMAP.md`](ROADMAP.md) |
|
|
81
|
+
| Changelog | [`CHANGELOG.md`](CHANGELOG.md) |
|
|
82
|
+
| Release process | [`RELEASING.md`](RELEASING.md) |
|
|
83
|
+
| Glossary | [`TERMS.md`](TERMS.md) |
|
|
84
|
+
| Architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) |
|
|
85
|
+
| Agent/CLI execution | [`AGENTS.md`](AGENTS.md) |
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT. See [`LICENSE`](../LICENSE) at the repository root.
|