@workflow-cannon/workspace-kit 0.16.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/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/task-engine/doctor-planning-persistence.d.ts +9 -0
- package/dist/modules/task-engine/doctor-planning-persistence.js +77 -0
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type DoctorPlanningIssue = {
|
|
2
|
+
path: string;
|
|
3
|
+
reason: string;
|
|
4
|
+
};
|
|
5
|
+
/** Resolve layered config and run SQLite planning persistence checks for `workspace-kit doctor`. */
|
|
6
|
+
export declare function collectDoctorPlanningPersistenceIssues(cwd: string): Promise<DoctorPlanningIssue[]>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ModuleRegistry } from "../core/module-registry.js";
|
|
2
|
+
import { resolveWorkspaceConfigWithLayers } from "../core/workspace-kit-config.js";
|
|
3
|
+
import { validatePlanningPersistenceForDoctor } from "../modules/task-engine/doctor-planning-persistence.js";
|
|
4
|
+
import { documentationModule } from "../modules/documentation/index.js";
|
|
5
|
+
import { taskEngineModule } from "../modules/task-engine/index.js";
|
|
6
|
+
import { approvalsModule } from "../modules/approvals/index.js";
|
|
7
|
+
import { planningModule } from "../modules/planning/index.js";
|
|
8
|
+
import { improvementModule } from "../modules/improvement/index.js";
|
|
9
|
+
import { workspaceConfigModule } from "../modules/workspace-config/index.js";
|
|
10
|
+
const defaultRegistryModules = [
|
|
11
|
+
workspaceConfigModule,
|
|
12
|
+
documentationModule,
|
|
13
|
+
taskEngineModule,
|
|
14
|
+
approvalsModule,
|
|
15
|
+
planningModule,
|
|
16
|
+
improvementModule
|
|
17
|
+
];
|
|
18
|
+
/** Resolve layered config and run SQLite planning persistence checks for `workspace-kit doctor`. */
|
|
19
|
+
export async function collectDoctorPlanningPersistenceIssues(cwd) {
|
|
20
|
+
try {
|
|
21
|
+
const registry = new ModuleRegistry(defaultRegistryModules);
|
|
22
|
+
const { effective } = await resolveWorkspaceConfigWithLayers({
|
|
23
|
+
workspacePath: cwd,
|
|
24
|
+
registry,
|
|
25
|
+
invocationConfig: {}
|
|
26
|
+
});
|
|
27
|
+
return validatePlanningPersistenceForDoctor(cwd, effective);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
path: "workspace-config",
|
|
33
|
+
reason: `config-resolution-failed: ${err.message}`
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
import { AGENT_CLI_MAP_HUMAN_DOC, appendPolicyTrace, parsePolicyApprovalFromEnv, resolveActorWithFallback } from "./core/policy.js";
|
|
6
6
|
import { runWorkspaceConfigCli } from "./core/config-cli.js";
|
|
7
7
|
import { handleRunCommand } from "./cli/run-command.js";
|
|
8
|
+
import { collectDoctorPlanningPersistenceIssues } from "./cli/doctor-planning-issues.js";
|
|
8
9
|
const EXIT_SUCCESS = 0;
|
|
9
10
|
const EXIT_VALIDATION_FAILURE = 1;
|
|
10
11
|
const EXIT_USAGE_ERROR = 2;
|
|
@@ -606,6 +607,7 @@ export async function runCli(args, options = {}) {
|
|
|
606
607
|
});
|
|
607
608
|
}
|
|
608
609
|
}
|
|
610
|
+
issues.push(...(await collectDoctorPlanningPersistenceIssues(cwd)));
|
|
609
611
|
if (issues.length > 0) {
|
|
610
612
|
writeError("workspace-kit doctor failed validation.");
|
|
611
613
|
for (const issue of issues) {
|
|
@@ -615,6 +617,7 @@ export async function runCli(args, options = {}) {
|
|
|
615
617
|
}
|
|
616
618
|
writeLine("workspace-kit doctor passed.");
|
|
617
619
|
writeLine("All canonical workspace-kit contract files are present and parseable JSON.");
|
|
620
|
+
writeLine("Effective workspace config resolved; task planning persistence checks passed (including SQLite when configured).");
|
|
618
621
|
writeLine(`Next: workspace-kit run — list module commands; see ${AGENT_CLI_MAP_HUMAN_DOC} for tier/policy copy-paste.`);
|
|
619
622
|
return EXIT_SUCCESS;
|
|
620
623
|
}
|
|
@@ -16,6 +16,46 @@ const REGISTRY = {
|
|
|
16
16
|
exposure: "public",
|
|
17
17
|
writableLayers: ["project", "user"]
|
|
18
18
|
},
|
|
19
|
+
"tasks.wishlistStoreRelativePath": {
|
|
20
|
+
key: "tasks.wishlistStoreRelativePath",
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Relative path (from workspace root) to the Wishlist JSON store when persistenceBackend is json.",
|
|
23
|
+
default: ".workspace-kit/wishlist/state.json",
|
|
24
|
+
domainScope: "project",
|
|
25
|
+
owningModule: "task-engine",
|
|
26
|
+
sensitive: false,
|
|
27
|
+
requiresRestart: false,
|
|
28
|
+
requiresApproval: false,
|
|
29
|
+
exposure: "public",
|
|
30
|
+
writableLayers: ["project", "user"]
|
|
31
|
+
},
|
|
32
|
+
"tasks.persistenceBackend": {
|
|
33
|
+
key: "tasks.persistenceBackend",
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "Task + wishlist persistence: json (default) or sqlite.",
|
|
36
|
+
default: "json",
|
|
37
|
+
allowedValues: ["json", "sqlite"],
|
|
38
|
+
domainScope: "project",
|
|
39
|
+
owningModule: "task-engine",
|
|
40
|
+
sensitive: false,
|
|
41
|
+
requiresRestart: false,
|
|
42
|
+
requiresApproval: false,
|
|
43
|
+
exposure: "public",
|
|
44
|
+
writableLayers: ["project", "user"]
|
|
45
|
+
},
|
|
46
|
+
"tasks.sqliteDatabaseRelativePath": {
|
|
47
|
+
key: "tasks.sqliteDatabaseRelativePath",
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Relative path (from workspace root) to the SQLite file when persistenceBackend is sqlite.",
|
|
50
|
+
default: ".workspace-kit/tasks/workspace-kit.db",
|
|
51
|
+
domainScope: "project",
|
|
52
|
+
owningModule: "task-engine",
|
|
53
|
+
sensitive: false,
|
|
54
|
+
requiresRestart: false,
|
|
55
|
+
requiresApproval: false,
|
|
56
|
+
exposure: "public",
|
|
57
|
+
writableLayers: ["project", "user"]
|
|
58
|
+
},
|
|
19
59
|
"policy.extraSensitiveModuleCommands": {
|
|
20
60
|
key: "policy.extraSensitiveModuleCommands",
|
|
21
61
|
type: "array",
|
|
@@ -315,10 +355,25 @@ export function validatePersistedConfigDocument(data, label) {
|
|
|
315
355
|
}
|
|
316
356
|
const t = tasks;
|
|
317
357
|
for (const k of Object.keys(t)) {
|
|
318
|
-
if (k !== "storeRelativePath"
|
|
358
|
+
if (k !== "storeRelativePath" &&
|
|
359
|
+
k !== "wishlistStoreRelativePath" &&
|
|
360
|
+
k !== "persistenceBackend" &&
|
|
361
|
+
k !== "sqliteDatabaseRelativePath") {
|
|
319
362
|
throw new Error(`config-invalid(${label}): unknown tasks.${k}`);
|
|
320
363
|
}
|
|
321
364
|
}
|
|
365
|
+
if (t.storeRelativePath !== undefined) {
|
|
366
|
+
validateValueForMetadata(REGISTRY["tasks.storeRelativePath"], t.storeRelativePath);
|
|
367
|
+
}
|
|
368
|
+
if (t.wishlistStoreRelativePath !== undefined) {
|
|
369
|
+
validateValueForMetadata(REGISTRY["tasks.wishlistStoreRelativePath"], t.wishlistStoreRelativePath);
|
|
370
|
+
}
|
|
371
|
+
if (t.persistenceBackend !== undefined) {
|
|
372
|
+
validateValueForMetadata(REGISTRY["tasks.persistenceBackend"], t.persistenceBackend);
|
|
373
|
+
}
|
|
374
|
+
if (t.sqliteDatabaseRelativePath !== undefined) {
|
|
375
|
+
validateValueForMetadata(REGISTRY["tasks.sqliteDatabaseRelativePath"], t.sqliteDatabaseRelativePath);
|
|
376
|
+
}
|
|
322
377
|
}
|
|
323
378
|
const policy = data.policy;
|
|
324
379
|
if (policy !== undefined) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type DoctorPlanningIssue = {
|
|
2
|
+
path: string;
|
|
3
|
+
reason: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* When effective config selects SQLite for task/wishlist persistence, verify the DB file exists
|
|
7
|
+
* and can be opened read-only; if a planning row is present, validate embedded JSON schemaVersion.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validatePlanningPersistenceForDoctor(workspacePath: string, effectiveConfig: Record<string, unknown>): DoctorPlanningIssue[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import Database from "better-sqlite3";
|
|
4
|
+
import { getTaskPersistenceBackend, planningSqliteDatabaseRelativePath } from "./planning-config.js";
|
|
5
|
+
/**
|
|
6
|
+
* When effective config selects SQLite for task/wishlist persistence, verify the DB file exists
|
|
7
|
+
* and can be opened read-only; if a planning row is present, validate embedded JSON schemaVersion.
|
|
8
|
+
*/
|
|
9
|
+
export function validatePlanningPersistenceForDoctor(workspacePath, effectiveConfig) {
|
|
10
|
+
const issues = [];
|
|
11
|
+
if (getTaskPersistenceBackend(effectiveConfig) !== "sqlite") {
|
|
12
|
+
return issues;
|
|
13
|
+
}
|
|
14
|
+
const ctx = { workspacePath, effectiveConfig };
|
|
15
|
+
const dbRel = planningSqliteDatabaseRelativePath(ctx);
|
|
16
|
+
const dbAbs = path.resolve(workspacePath, dbRel);
|
|
17
|
+
const relDisplay = path.relative(workspacePath, dbAbs) || dbAbs;
|
|
18
|
+
if (!fs.existsSync(dbAbs)) {
|
|
19
|
+
issues.push({
|
|
20
|
+
path: relDisplay,
|
|
21
|
+
reason: "sqlite-planning-db-missing (tasks.persistenceBackend is sqlite; run migrate-task-persistence json-to-sqlite or fix tasks.sqliteDatabaseRelativePath)"
|
|
22
|
+
});
|
|
23
|
+
return issues;
|
|
24
|
+
}
|
|
25
|
+
let db;
|
|
26
|
+
try {
|
|
27
|
+
db = new Database(dbAbs, { readonly: true });
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
issues.push({
|
|
31
|
+
path: relDisplay,
|
|
32
|
+
reason: `sqlite-open-failed: ${err.message}`
|
|
33
|
+
});
|
|
34
|
+
return issues;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const row = db
|
|
38
|
+
.prepare("SELECT task_store_json, wishlist_store_json FROM workspace_planning_state WHERE id = 1")
|
|
39
|
+
.get();
|
|
40
|
+
if (row) {
|
|
41
|
+
try {
|
|
42
|
+
const taskDoc = JSON.parse(row.task_store_json);
|
|
43
|
+
if (taskDoc.schemaVersion !== 1) {
|
|
44
|
+
issues.push({
|
|
45
|
+
path: relDisplay,
|
|
46
|
+
reason: `sqlite-task_store_json: unsupported schemaVersion (expected 1, got ${taskDoc.schemaVersion})`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
issues.push({ path: relDisplay, reason: "sqlite-task_store_json: invalid JSON" });
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const wishDoc = JSON.parse(row.wishlist_store_json);
|
|
55
|
+
if (wishDoc.schemaVersion !== 1) {
|
|
56
|
+
issues.push({
|
|
57
|
+
path: relDisplay,
|
|
58
|
+
reason: `sqlite-wishlist_store_json: unsupported schemaVersion (expected 1, got ${wishDoc.schemaVersion})`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
issues.push({ path: relDisplay, reason: "sqlite-wishlist_store_json: invalid JSON" });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
issues.push({
|
|
69
|
+
path: relDisplay,
|
|
70
|
+
reason: `sqlite-schema-invalid: ${err.message}`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
db.close();
|
|
75
|
+
}
|
|
76
|
+
return issues;
|
|
77
|
+
}
|