@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 CHANGED
@@ -61,9 +61,9 @@ This keeps automation adaptive without sacrificing safety, governance, or develo
61
61
 
62
62
  ## Current Status
63
63
 
64
- - Project tracking has been reset for split-repo execution.
65
- - Current execution phase is **Phase 0 (foundation)**.
66
- - Phase 0 opens with `T178` to `T180` for release hardening and consumer cadence definition.
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 ctx = { runtimeVersion: "0.1", workspacePath: cwd };
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>;