@workflow-cannon/workspace-kit 0.3.0 → 0.4.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 +3 -3
- package/dist/cli.js +115 -3
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +14 -0
- package/dist/core/config-cli.d.ts +6 -0
- package/dist/core/config-cli.js +479 -0
- package/dist/core/config-metadata.d.ts +35 -0
- package/dist/core/config-metadata.js +162 -0
- package/dist/core/config-mutations.d.ts +18 -0
- package/dist/core/config-mutations.js +32 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/policy.d.ts +34 -0
- package/dist/core/policy.js +135 -0
- package/dist/core/workspace-kit-config.d.ts +57 -0
- package/dist/core/workspace-kit-config.js +266 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/modules/task-engine/index.js +15 -3
- package/dist/modules/workspace-config/index.d.ts +2 -0
- package/dist/modules/workspace-config/index.js +100 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,9 +61,9 @@ This keeps automation adaptive without sacrificing safety, governance, or develo
|
|
|
61
61
|
|
|
62
62
|
## Current Status
|
|
63
63
|
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
- Phase
|
|
64
|
+
- **Phase 0** and **Phase 1** (task engine, `v0.3.0`) are complete.
|
|
65
|
+
- **Phase 2** (layered config, policy gates, cutover docs, `v0.4.0`) is complete in-repo; see `docs/maintainers/TASKS.md` and `docs/maintainers/ROADMAP.md`.
|
|
66
|
+
- **Phase 2b** (policy + config UX, `v0.4.1`) is complete in-repo. **Phase 3** (enhancement loop MVP, `v0.5.0`) is next (`T190` onward).
|
|
67
67
|
|
|
68
68
|
## Goals
|
|
69
69
|
|
package/dist/cli.js
CHANGED
|
@@ -4,11 +4,15 @@ import path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { ModuleRegistry } from "./core/module-registry.js";
|
|
6
6
|
import { ModuleCommandRouter } from "./core/module-command-router.js";
|
|
7
|
+
import { appendPolicyTrace, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, resolveActor, resolvePolicyOperationIdForCommand } from "./core/policy.js";
|
|
8
|
+
import { runWorkspaceConfigCli } from "./core/config-cli.js";
|
|
9
|
+
import { resolveWorkspaceConfigWithLayers } from "./core/workspace-kit-config.js";
|
|
7
10
|
import { documentationModule } from "./modules/documentation/index.js";
|
|
8
11
|
import { taskEngineModule } from "./modules/task-engine/index.js";
|
|
9
12
|
import { approvalsModule } from "./modules/approvals/index.js";
|
|
10
13
|
import { planningModule } from "./modules/planning/index.js";
|
|
11
14
|
import { improvementModule } from "./modules/improvement/index.js";
|
|
15
|
+
import { workspaceConfigModule } from "./modules/workspace-config/index.js";
|
|
12
16
|
const EXIT_SUCCESS = 0;
|
|
13
17
|
const EXIT_VALIDATION_FAILURE = 1;
|
|
14
18
|
const EXIT_USAGE_ERROR = 2;
|
|
@@ -94,6 +98,33 @@ export async function parseJsonFile(filePath) {
|
|
|
94
98
|
const raw = await fs.readFile(filePath, "utf8");
|
|
95
99
|
return JSON.parse(raw);
|
|
96
100
|
}
|
|
101
|
+
async function requireCliPolicyApproval(cwd, operationId, commandLabel, writeError) {
|
|
102
|
+
const approval = parsePolicyApprovalFromEnv(process.env);
|
|
103
|
+
if (!approval) {
|
|
104
|
+
writeError(`workspace-kit ${commandLabel} requires WORKSPACE_KIT_POLICY_APPROVAL with JSON {"confirmed":true,"rationale":"..."} (agent-mediated).`);
|
|
105
|
+
await appendPolicyTrace(cwd, {
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
operationId,
|
|
108
|
+
command: commandLabel,
|
|
109
|
+
actor: resolveActor(cwd, {}, process.env),
|
|
110
|
+
allowed: false,
|
|
111
|
+
message: "missing WORKSPACE_KIT_POLICY_APPROVAL"
|
|
112
|
+
});
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return { rationale: approval.rationale };
|
|
116
|
+
}
|
|
117
|
+
async function recordCliPolicySuccess(cwd, operationId, commandLabel, rationale, commandOk) {
|
|
118
|
+
await appendPolicyTrace(cwd, {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
operationId,
|
|
121
|
+
command: commandLabel,
|
|
122
|
+
actor: resolveActor(cwd, {}, process.env),
|
|
123
|
+
allowed: true,
|
|
124
|
+
rationale,
|
|
125
|
+
commandOk
|
|
126
|
+
});
|
|
127
|
+
}
|
|
97
128
|
function readStringField(objectValue, key, errors, fieldPath) {
|
|
98
129
|
const value = objectValue[key];
|
|
99
130
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
@@ -312,12 +343,20 @@ export async function runCli(args, options = {}) {
|
|
|
312
343
|
const writeError = options.writeError ?? console.error;
|
|
313
344
|
const [command] = args;
|
|
314
345
|
if (!command) {
|
|
315
|
-
writeError("Usage: workspace-kit <init|doctor|check|upgrade|drift-check|run>");
|
|
346
|
+
writeError("Usage: workspace-kit <init|doctor|check|upgrade|drift-check|run|config>");
|
|
316
347
|
return EXIT_USAGE_ERROR;
|
|
317
348
|
}
|
|
349
|
+
if (command === "config") {
|
|
350
|
+
return runWorkspaceConfigCli(cwd, args.slice(1), { writeLine, writeError });
|
|
351
|
+
}
|
|
318
352
|
if (command === "init") {
|
|
353
|
+
const approval = await requireCliPolicyApproval(cwd, "cli.init", "init", writeError);
|
|
354
|
+
if (!approval) {
|
|
355
|
+
return EXIT_VALIDATION_FAILURE;
|
|
356
|
+
}
|
|
319
357
|
const { errors, profile } = await validateProfile(cwd);
|
|
320
358
|
if (errors.length > 0 || !profile) {
|
|
359
|
+
await recordCliPolicySuccess(cwd, "cli.init", "init", approval.rationale, false);
|
|
321
360
|
writeError("workspace-kit init failed profile validation.");
|
|
322
361
|
for (const error of errors) {
|
|
323
362
|
writeError(`- ${error}`);
|
|
@@ -328,6 +367,7 @@ export async function runCli(args, options = {}) {
|
|
|
328
367
|
writeLine("workspace-kit init generated profile-driven project context artifacts.");
|
|
329
368
|
writeLine(`- ${path.relative(cwd, artifacts.generatedJsonPath)}`);
|
|
330
369
|
writeLine(`- ${path.relative(cwd, artifacts.generatedRulePath)}`);
|
|
370
|
+
await recordCliPolicySuccess(cwd, "cli.init", "init", approval.rationale, true);
|
|
331
371
|
return EXIT_SUCCESS;
|
|
332
372
|
}
|
|
333
373
|
if (command === "check") {
|
|
@@ -344,8 +384,13 @@ export async function runCli(args, options = {}) {
|
|
|
344
384
|
return EXIT_SUCCESS;
|
|
345
385
|
}
|
|
346
386
|
if (command === "upgrade") {
|
|
387
|
+
const approval = await requireCliPolicyApproval(cwd, "cli.upgrade", "upgrade", writeError);
|
|
388
|
+
if (!approval) {
|
|
389
|
+
return EXIT_VALIDATION_FAILURE;
|
|
390
|
+
}
|
|
347
391
|
const { errors, profile } = await validateProfile(cwd);
|
|
348
392
|
if (errors.length > 0 || !profile) {
|
|
393
|
+
await recordCliPolicySuccess(cwd, "cli.upgrade", "upgrade", approval.rationale, false);
|
|
349
394
|
writeError("workspace-kit upgrade failed profile validation.");
|
|
350
395
|
for (const error of errors) {
|
|
351
396
|
writeError(`- ${error}`);
|
|
@@ -444,6 +489,7 @@ export async function runCli(args, options = {}) {
|
|
|
444
489
|
}
|
|
445
490
|
}
|
|
446
491
|
writeLine(`Backups written under: ${path.relative(cwd, backupRoot)}`);
|
|
492
|
+
await recordCliPolicySuccess(cwd, "cli.upgrade", "upgrade", approval.rationale, true);
|
|
447
493
|
return EXIT_SUCCESS;
|
|
448
494
|
}
|
|
449
495
|
if (command === "drift-check") {
|
|
@@ -534,6 +580,7 @@ export async function runCli(args, options = {}) {
|
|
|
534
580
|
}
|
|
535
581
|
if (command === "run") {
|
|
536
582
|
const allModules = [
|
|
583
|
+
workspaceConfigModule,
|
|
537
584
|
documentationModule,
|
|
538
585
|
taskEngineModule,
|
|
539
586
|
approvalsModule,
|
|
@@ -571,9 +618,74 @@ export async function runCli(args, options = {}) {
|
|
|
571
618
|
return EXIT_USAGE_ERROR;
|
|
572
619
|
}
|
|
573
620
|
}
|
|
574
|
-
const
|
|
621
|
+
const invocationConfig = typeof commandArgs.config === "object" &&
|
|
622
|
+
commandArgs.config !== null &&
|
|
623
|
+
!Array.isArray(commandArgs.config)
|
|
624
|
+
? commandArgs.config
|
|
625
|
+
: {};
|
|
626
|
+
let effective;
|
|
627
|
+
try {
|
|
628
|
+
const resolved = await resolveWorkspaceConfigWithLayers({
|
|
629
|
+
workspacePath: cwd,
|
|
630
|
+
registry,
|
|
631
|
+
invocationConfig
|
|
632
|
+
});
|
|
633
|
+
effective = resolved.effective;
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
637
|
+
writeError(`Config resolution failed: ${message}`);
|
|
638
|
+
return EXIT_VALIDATION_FAILURE;
|
|
639
|
+
}
|
|
640
|
+
const actor = resolveActor(cwd, commandArgs, process.env);
|
|
641
|
+
const sensitive = isSensitiveModuleCommandForEffective(subcommand, commandArgs, effective);
|
|
642
|
+
if (sensitive) {
|
|
643
|
+
const approval = parsePolicyApproval(commandArgs);
|
|
644
|
+
if (!approval) {
|
|
645
|
+
const op = resolvePolicyOperationIdForCommand(subcommand, effective);
|
|
646
|
+
if (op) {
|
|
647
|
+
await appendPolicyTrace(cwd, {
|
|
648
|
+
timestamp: new Date().toISOString(),
|
|
649
|
+
operationId: op,
|
|
650
|
+
command: `run ${subcommand}`,
|
|
651
|
+
actor,
|
|
652
|
+
allowed: false,
|
|
653
|
+
message: "missing policyApproval in JSON args"
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
writeLine(JSON.stringify({
|
|
657
|
+
ok: false,
|
|
658
|
+
code: "policy-denied",
|
|
659
|
+
message: 'Sensitive command requires policyApproval in JSON args: {"policyApproval":{"confirmed":true,"rationale":"user approved in chat"}}'
|
|
660
|
+
}, null, 2));
|
|
661
|
+
return EXIT_VALIDATION_FAILURE;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const ctx = {
|
|
665
|
+
runtimeVersion: "0.1",
|
|
666
|
+
workspacePath: cwd,
|
|
667
|
+
effectiveConfig: effective,
|
|
668
|
+
resolvedActor: actor,
|
|
669
|
+
moduleRegistry: registry
|
|
670
|
+
};
|
|
575
671
|
try {
|
|
576
672
|
const result = await router.execute(subcommand, commandArgs, ctx);
|
|
673
|
+
if (sensitive) {
|
|
674
|
+
const approval = parsePolicyApproval(commandArgs);
|
|
675
|
+
const op = resolvePolicyOperationIdForCommand(subcommand, effective);
|
|
676
|
+
if (approval && op) {
|
|
677
|
+
await appendPolicyTrace(cwd, {
|
|
678
|
+
timestamp: new Date().toISOString(),
|
|
679
|
+
operationId: op,
|
|
680
|
+
command: `run ${subcommand}`,
|
|
681
|
+
actor,
|
|
682
|
+
allowed: true,
|
|
683
|
+
rationale: approval.rationale,
|
|
684
|
+
commandOk: result.ok,
|
|
685
|
+
message: result.message
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
}
|
|
577
689
|
writeLine(JSON.stringify(result, null, 2));
|
|
578
690
|
return result.ok ? EXIT_SUCCESS : EXIT_VALIDATION_FAILURE;
|
|
579
691
|
}
|
|
@@ -584,7 +696,7 @@ export async function runCli(args, options = {}) {
|
|
|
584
696
|
}
|
|
585
697
|
}
|
|
586
698
|
if (command !== "doctor") {
|
|
587
|
-
writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check, run.`);
|
|
699
|
+
writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check, run, config.`);
|
|
588
700
|
return EXIT_USAGE_ERROR;
|
|
589
701
|
}
|
|
590
702
|
const issues = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { ModuleCommand, ModuleCommandResult, ModuleCapability, ModuleDocumentContract, ModuleEvent, ModuleInstructionContract, ModuleInstructionEntry, ModuleLifecycleContext, ModuleRegistration, WorkflowModule } from "./module-contract.js";
|
|
1
|
+
export type { ConfigRegistryView, ModuleCommand, ModuleCommandResult, ModuleCapability, ModuleDocumentContract, ModuleEvent, ModuleInstructionContract, ModuleInstructionEntry, ModuleLifecycleContext, ModuleRegistration, WorkflowModule } from "./module-contract.js";
|
|
@@ -36,9 +36,23 @@ export type ModuleCommandResult = {
|
|
|
36
36
|
message?: string;
|
|
37
37
|
data?: Record<string, unknown>;
|
|
38
38
|
};
|
|
39
|
+
/** Subset of module registry used for config layer ordering (avoids core↔contracts cycles). */
|
|
40
|
+
export type ConfigRegistryView = {
|
|
41
|
+
getStartupOrder(): ReadonlyArray<{
|
|
42
|
+
registration: {
|
|
43
|
+
id: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
39
47
|
export type ModuleLifecycleContext = {
|
|
40
48
|
runtimeVersion: string;
|
|
41
49
|
workspacePath: string;
|
|
50
|
+
/** Merged workspace config (kit → modules → project → env → invocation). */
|
|
51
|
+
effectiveConfig?: Record<string, unknown>;
|
|
52
|
+
/** Resolved actor for policy traces (see phase2 workbook). */
|
|
53
|
+
resolvedActor?: string;
|
|
54
|
+
/** CLI supplies registry for explain-config and config resolution. */
|
|
55
|
+
moduleRegistry?: ConfigRegistryView;
|
|
42
56
|
};
|
|
43
57
|
export type ModuleRegistration = {
|
|
44
58
|
id: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type ConfigCliIo = {
|
|
2
|
+
writeLine: (s: string) => void;
|
|
3
|
+
writeError: (s: string) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare function generateConfigReferenceDocs(workspacePath: string): Promise<void>;
|
|
6
|
+
export declare function runWorkspaceConfigCli(cwd: string, argv: string[], io: ConfigCliIo): Promise<number>;
|