@workflow-cannon/workspace-kit 0.8.0 → 0.10.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.
Files changed (44) hide show
  1. package/README.md +21 -5
  2. package/dist/cli/run-command.d.ts +11 -0
  3. package/dist/cli/run-command.js +143 -0
  4. package/dist/cli.js +19 -146
  5. package/dist/core/config-cli.js +5 -5
  6. package/dist/core/config-metadata.js +3 -3
  7. package/dist/core/index.d.ts +1 -1
  8. package/dist/core/index.js +1 -1
  9. package/dist/core/policy.d.ts +11 -1
  10. package/dist/core/policy.js +41 -22
  11. package/dist/core/transcript-completion-hook.js +41 -3
  12. package/dist/core/workspace-kit-config.d.ts +2 -1
  13. package/dist/core/workspace-kit-config.js +4 -29
  14. package/dist/modules/approvals/index.js +2 -2
  15. package/dist/modules/documentation/runtime.js +30 -6
  16. package/dist/modules/improvement/index.js +15 -3
  17. package/dist/modules/improvement/transcript-sync-runtime.d.ts +5 -0
  18. package/dist/modules/improvement/transcript-sync-runtime.js +17 -0
  19. package/package.json +2 -1
  20. package/src/modules/documentation/README.md +39 -0
  21. package/src/modules/documentation/RULES.md +70 -0
  22. package/src/modules/documentation/config.md +14 -0
  23. package/src/modules/documentation/index.ts +120 -0
  24. package/src/modules/documentation/instructions/document-project.md +44 -0
  25. package/src/modules/documentation/instructions/documentation-maintainer.md +81 -0
  26. package/src/modules/documentation/instructions/generate-document.md +44 -0
  27. package/src/modules/documentation/runtime.ts +870 -0
  28. package/src/modules/documentation/schemas/documentation-schema.md +54 -0
  29. package/src/modules/documentation/state.md +8 -0
  30. package/src/modules/documentation/templates/AGENTS.md +84 -0
  31. package/src/modules/documentation/templates/ARCHITECTURE.md +71 -0
  32. package/src/modules/documentation/templates/PRINCIPLES.md +122 -0
  33. package/src/modules/documentation/templates/RELEASING.md +96 -0
  34. package/src/modules/documentation/templates/ROADMAP.md +131 -0
  35. package/src/modules/documentation/templates/SECURITY.md +53 -0
  36. package/src/modules/documentation/templates/SUPPORT.md +40 -0
  37. package/src/modules/documentation/templates/TERMS.md +61 -0
  38. package/src/modules/documentation/templates/runbooks/consumer-cadence.md +55 -0
  39. package/src/modules/documentation/templates/runbooks/parity-validation-flow.md +68 -0
  40. package/src/modules/documentation/templates/runbooks/release-channels.md +30 -0
  41. package/src/modules/documentation/templates/workbooks/phase2-config-policy-workbook.md +42 -0
  42. package/src/modules/documentation/templates/workbooks/task-engine-workbook.md +42 -0
  43. package/src/modules/documentation/templates/workbooks/transcript-automation-baseline.md +68 -0
  44. package/src/modules/documentation/types.ts +51 -0
package/README.md CHANGED
@@ -61,10 +61,12 @@ This keeps automation adaptive without sacrificing safety, governance, or develo
61
61
 
62
62
  ## Current Status
63
63
 
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 `.workspace-kit/tasks/state.json` and `docs/maintainers/ROADMAP.md`.
66
- - **Phase 2b** (policy + config UX, `v0.4.1`) and **Phase 3** (enhancement loop MVP, `v0.5.0`) are complete in-repo: evidence-driven **improvement** tasks, **`approvals`** (`review-item`), heuristic confidence, and append-only lineage.
67
- - **Phase 4** (`v0.6.0`) is complete in-repo: compatibility matrix/gates, diagnostics/SLO baseline evidence, release-channel mapping, and planning-doc consistency checks.
64
+ **Release and phase truth:** see `docs/maintainers/ROADMAP.md` and `docs/maintainers/data/workspace-kit-status.yaml`. **Task queue:** `.workspace-kit/tasks/state.json` (ids and `status` are authoritative for execution).
65
+
66
+ - **Phases 0–7** are complete through **`v0.9.0`** (see roadmap for slice ids).
67
+ - **Phase 8** ships maintainer/onboarding hardening (`v0.10.0`): policy denial clarity, runbooks, and doc alignment for CLI vs `run` approval.
68
+
69
+ Historical note: this file’s milestone list is not the live queue—always check task state for **`ready`** work.
68
70
 
69
71
  ## Goals
70
72
 
@@ -82,6 +84,18 @@ Install:
82
84
  npm install @workflow-cannon/workspace-kit
83
85
  ```
84
86
 
87
+ ### How to run the CLI (this repo and consumers)
88
+
89
+ There is **no** IDE slash command like `/qt` defined by this package unless your own editor config adds one. Supported entrypoints:
90
+
91
+ | Context | Command |
92
+ | --- | --- |
93
+ | **Installed package** | `npx @workflow-cannon/workspace-kit --help` or `pnpm exec workspace-kit --help` when the package is a dependency |
94
+ | **Developing this repo** | `pnpm run build` then `node dist/cli.js --help` or `pnpm exec workspace-kit --help` if linked |
95
+ | **Transcript helpers** | `pnpm run transcript:sync` / `pnpm run transcript:ingest` (see maintainer runbooks) |
96
+
97
+ Mutating commands require policy approval: **`docs/maintainers/POLICY-APPROVAL.md`** (JSON **`policyApproval`** for `workspace-kit run`, env for `config`/`init`/`upgrade`).
98
+
85
99
  ## Repository Map
86
100
 
87
101
  - `README.md` - project entry point
@@ -104,8 +118,10 @@ npm install @workflow-cannon/workspace-kit
104
118
  - Glossary and agent-guidance terms: `docs/maintainers/TERMS.md`
105
119
  - Architecture direction: `docs/maintainers/ARCHITECTURE.md`
106
120
  - Project decisions: `docs/maintainers/DECISIONS.md`
107
- - Contribution guidelines: `docs/maintainers/CONTRIBUTING.md`
121
+ - Governance policy surface: `docs/maintainers/GOVERNANCE.md`
108
122
  - Release process and gates: `docs/maintainers/RELEASING.md`
123
+ - Policy / approval surfaces: `docs/maintainers/POLICY-APPROVAL.md`
124
+ - Canonical changelog: `docs/maintainers/CHANGELOG.md` (`CHANGELOG.md` at repo root is pointer-only)
109
125
  - Canonical AI module build guidance: `.ai/module-build.md`
110
126
  - Human module build guide: `docs/maintainers/module-build-guide.md`
111
127
  - Security, support, and governance: `docs/maintainers/SECURITY.md`, `docs/maintainers/SUPPORT.md`, `docs/maintainers/GOVERNANCE.md`
@@ -0,0 +1,11 @@
1
+ export type RunCommandIo = {
2
+ writeLine: (message: string) => void;
3
+ writeError: (message: string) => void;
4
+ };
5
+ export type RunCommandExitCodes = {
6
+ success: number;
7
+ validationFailure: number;
8
+ usageError: number;
9
+ internalError: number;
10
+ };
11
+ export declare function handleRunCommand(cwd: string, args: string[], io: RunCommandIo, codes: RunCommandExitCodes): Promise<number>;
@@ -0,0 +1,143 @@
1
+ import { ModuleRegistry } from "../core/module-registry.js";
2
+ import { ModuleCommandRouter } from "../core/module-command-router.js";
3
+ import { appendPolicyTrace, isSensitiveModuleCommandForEffective, parsePolicyApproval, POLICY_APPROVAL_HUMAN_DOC, resolveActorWithFallback, resolvePolicyOperationIdForCommand } from "../core/policy.js";
4
+ import { getSessionGrant, recordSessionGrant, resolveSessionId } from "../core/session-policy.js";
5
+ import { applyResponseTemplateApplication } from "../core/response-template-shaping.js";
6
+ import { resolveWorkspaceConfigWithLayers } from "../core/workspace-kit-config.js";
7
+ import { documentationModule } from "../modules/documentation/index.js";
8
+ import { taskEngineModule } from "../modules/task-engine/index.js";
9
+ import { approvalsModule } from "../modules/approvals/index.js";
10
+ import { planningModule } from "../modules/planning/index.js";
11
+ import { improvementModule } from "../modules/improvement/index.js";
12
+ import { workspaceConfigModule } from "../modules/workspace-config/index.js";
13
+ export async function handleRunCommand(cwd, args, io, codes) {
14
+ const { writeLine, writeError } = io;
15
+ const allModules = [
16
+ workspaceConfigModule,
17
+ documentationModule,
18
+ taskEngineModule,
19
+ approvalsModule,
20
+ planningModule,
21
+ improvementModule
22
+ ];
23
+ const registry = new ModuleRegistry(allModules);
24
+ const router = new ModuleCommandRouter(registry);
25
+ const subcommand = args[1];
26
+ if (!subcommand) {
27
+ const commands = router.listCommands();
28
+ writeLine("Available module commands:");
29
+ for (const cmd of commands) {
30
+ const desc = cmd.description ? ` — ${cmd.description}` : "";
31
+ writeLine(` ${cmd.name} (${cmd.moduleId})${desc}`);
32
+ }
33
+ writeLine("");
34
+ writeLine("Usage: workspace-kit run <command> [json-args]");
35
+ return codes.success;
36
+ }
37
+ let commandArgs = {};
38
+ if (args[2]) {
39
+ try {
40
+ const parsed = JSON.parse(args[2]);
41
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
42
+ commandArgs = parsed;
43
+ }
44
+ else {
45
+ writeError("Command args must be a JSON object.");
46
+ return codes.usageError;
47
+ }
48
+ }
49
+ catch {
50
+ writeError(`Invalid JSON args: ${args[2]}`);
51
+ return codes.usageError;
52
+ }
53
+ }
54
+ const invocationConfig = typeof commandArgs.config === "object" &&
55
+ commandArgs.config !== null &&
56
+ !Array.isArray(commandArgs.config)
57
+ ? commandArgs.config
58
+ : {};
59
+ let effective;
60
+ try {
61
+ const resolved = await resolveWorkspaceConfigWithLayers({
62
+ workspacePath: cwd,
63
+ registry,
64
+ invocationConfig
65
+ });
66
+ effective = resolved.effective;
67
+ }
68
+ catch (err) {
69
+ const message = err instanceof Error ? err.message : String(err);
70
+ writeError(`Config resolution failed: ${message}`);
71
+ return codes.validationFailure;
72
+ }
73
+ const actor = await resolveActorWithFallback(cwd, commandArgs, process.env);
74
+ const sensitive = isSensitiveModuleCommandForEffective(subcommand, commandArgs, effective);
75
+ const sessionId = resolveSessionId(process.env);
76
+ const policyOp = resolvePolicyOperationIdForCommand(subcommand, effective);
77
+ const explicitPolicyApproval = parsePolicyApproval(commandArgs);
78
+ let resolvedSensitiveApproval = explicitPolicyApproval;
79
+ if (sensitive) {
80
+ if (!resolvedSensitiveApproval && policyOp) {
81
+ const grant = await getSessionGrant(cwd, policyOp, sessionId);
82
+ if (grant) {
83
+ resolvedSensitiveApproval = { confirmed: true, rationale: grant.rationale };
84
+ }
85
+ }
86
+ if (!resolvedSensitiveApproval) {
87
+ if (policyOp) {
88
+ await appendPolicyTrace(cwd, {
89
+ timestamp: new Date().toISOString(),
90
+ operationId: policyOp,
91
+ command: `run ${subcommand}`,
92
+ actor,
93
+ allowed: false,
94
+ message: "missing policyApproval in JSON args"
95
+ });
96
+ }
97
+ writeLine(JSON.stringify({
98
+ ok: false,
99
+ code: "policy-denied",
100
+ operationId: policyOp ?? null,
101
+ remediationDoc: POLICY_APPROVAL_HUMAN_DOC,
102
+ message: 'Sensitive command requires policyApproval in JSON args (or an existing session grant for this operation). Example: {"policyApproval":{"confirmed":true,"rationale":"why","scope":"session"}}. See remediationDoc for env vs JSON approval surfaces.',
103
+ hint: policyOp != null
104
+ ? `Operation ${policyOp} requires explicit approval; WORKSPACE_KIT_POLICY_APPROVAL is not read for workspace-kit run.`
105
+ : "Operation could not be mapped to policyOperationId; check policy.extraSensitiveModuleCommands and pass policyApproval in JSON args."
106
+ }, null, 2));
107
+ return codes.validationFailure;
108
+ }
109
+ }
110
+ const ctx = {
111
+ runtimeVersion: "0.1",
112
+ workspacePath: cwd,
113
+ effectiveConfig: effective,
114
+ resolvedActor: actor,
115
+ moduleRegistry: registry
116
+ };
117
+ try {
118
+ const rawResult = await router.execute(subcommand, commandArgs, ctx);
119
+ if (sensitive && resolvedSensitiveApproval && policyOp) {
120
+ await appendPolicyTrace(cwd, {
121
+ timestamp: new Date().toISOString(),
122
+ operationId: policyOp,
123
+ command: `run ${subcommand}`,
124
+ actor,
125
+ allowed: true,
126
+ rationale: resolvedSensitiveApproval.rationale,
127
+ commandOk: rawResult.ok,
128
+ message: rawResult.message
129
+ });
130
+ if (explicitPolicyApproval?.scope === "session" && rawResult.ok) {
131
+ await recordSessionGrant(cwd, policyOp, sessionId, explicitPolicyApproval.rationale);
132
+ }
133
+ }
134
+ const result = applyResponseTemplateApplication(subcommand, commandArgs, rawResult, effective);
135
+ writeLine(JSON.stringify(result, null, 2));
136
+ return result.ok ? codes.success : codes.validationFailure;
137
+ }
138
+ catch (error) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ writeError(`Module command failed: ${message}`);
141
+ return codes.internalError;
142
+ }
143
+ }
package/dist/cli.js CHANGED
@@ -2,23 +2,14 @@
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
- import { ModuleRegistry } from "./core/module-registry.js";
6
- import { ModuleCommandRouter } from "./core/module-command-router.js";
7
- import { appendPolicyTrace, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, resolveActor, resolvePolicyOperationIdForCommand } from "./core/policy.js";
8
- import { getSessionGrant, recordSessionGrant, resolveSessionId } from "./core/session-policy.js";
9
- import { applyResponseTemplateApplication } from "./core/response-template-shaping.js";
5
+ import { appendPolicyTrace, parsePolicyApprovalFromEnv, resolveActorWithFallback } from "./core/policy.js";
10
6
  import { runWorkspaceConfigCli } from "./core/config-cli.js";
11
- import { resolveWorkspaceConfigWithLayers } from "./core/workspace-kit-config.js";
12
- import { documentationModule } from "./modules/documentation/index.js";
13
- import { taskEngineModule } from "./modules/task-engine/index.js";
14
- import { approvalsModule } from "./modules/approvals/index.js";
15
- import { planningModule } from "./modules/planning/index.js";
16
- import { improvementModule } from "./modules/improvement/index.js";
17
- import { workspaceConfigModule } from "./modules/workspace-config/index.js";
7
+ import { handleRunCommand } from "./cli/run-command.js";
18
8
  const EXIT_SUCCESS = 0;
19
9
  const EXIT_VALIDATION_FAILURE = 1;
20
10
  const EXIT_USAGE_ERROR = 2;
21
11
  const EXIT_INTERNAL_ERROR = 3;
12
+ const CANONICAL_KIT_NAME = "@workflow-cannon/workspace-kit";
22
13
  export const defaultWorkspaceKitPaths = {
23
14
  profile: "workspace-kit.profile.json",
24
15
  profileSchema: "schemas/workspace-kit-profile.schema.json",
@@ -103,12 +94,12 @@ export async function parseJsonFile(filePath) {
103
94
  async function requireCliPolicyApproval(cwd, operationId, commandLabel, writeError) {
104
95
  const approval = parsePolicyApprovalFromEnv(process.env);
105
96
  if (!approval) {
106
- writeError(`workspace-kit ${commandLabel} requires WORKSPACE_KIT_POLICY_APPROVAL with JSON {"confirmed":true,"rationale":"..."} (agent-mediated).`);
97
+ writeError(`workspace-kit ${commandLabel} (${operationId}) requires WORKSPACE_KIT_POLICY_APPROVAL with JSON {"confirmed":true,"rationale":"..."} (agent-mediated). For workspace-kit run sensitive commands, use policyApproval in JSON args instead — see docs/maintainers/POLICY-APPROVAL.md.`);
107
98
  await appendPolicyTrace(cwd, {
108
99
  timestamp: new Date().toISOString(),
109
100
  operationId,
110
101
  command: commandLabel,
111
- actor: resolveActor(cwd, {}, process.env),
102
+ actor: await resolveActorWithFallback(cwd, {}, process.env),
112
103
  allowed: false,
113
104
  message: "missing WORKSPACE_KIT_POLICY_APPROVAL"
114
105
  });
@@ -121,7 +112,7 @@ async function recordCliPolicySuccess(cwd, operationId, commandLabel, rationale,
121
112
  timestamp: new Date().toISOString(),
122
113
  operationId,
123
114
  command: commandLabel,
124
- actor: resolveActor(cwd, {}, process.env),
115
+ actor: await resolveActorWithFallback(cwd, {}, process.env),
125
116
  allowed: true,
126
117
  rationale,
127
118
  commandOk
@@ -275,7 +266,8 @@ async function resolvePackageVersion(cwd) {
275
266
  const version = parsed.version;
276
267
  const name = parsed.name;
277
268
  if (typeof version === "string" && version.length > 0 && typeof name === "string") {
278
- if (name === "quicktask-workspace-kit" ||
269
+ if (name === CANONICAL_KIT_NAME ||
270
+ name === "quicktask-workspace-kit" ||
279
271
  candidatePath.endsWith("packages/workspace-kit/package.json")) {
280
272
  return version;
281
273
  }
@@ -420,11 +412,7 @@ export async function runCli(args, options = {}) {
420
412
  const mergedManifest = {
421
413
  schemaVersion: 1,
422
414
  kit: {
423
- name: typeof existingManifest.kit === "object" &&
424
- existingManifest.kit &&
425
- typeof existingManifest.kit.name === "string"
426
- ? existingManifest.kit.name
427
- : "quicktask-workspace-kit",
415
+ name: CANONICAL_KIT_NAME,
428
416
  version: typeof existingManifest.kit === "object" &&
429
417
  existingManifest.kit &&
430
418
  typeof existingManifest.kit.version === "string"
@@ -554,12 +542,14 @@ export async function runCli(args, options = {}) {
554
542
  if (!manifestKitName || !manifestKitVersion) {
555
543
  driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: kit.name and kit.version are required`);
556
544
  }
557
- else if (manifestKitName === "quicktask-workspace-kit" && packageVersion) {
545
+ else if ((manifestKitName === CANONICAL_KIT_NAME || manifestKitName === "quicktask-workspace-kit") &&
546
+ packageVersion) {
558
547
  if (manifestKitVersion !== packageVersion) {
559
548
  driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: kit.version (${manifestKitVersion}) does not match package version (${packageVersion})`);
560
549
  }
561
550
  }
562
- else if (manifestKitName !== "quicktask-workspace-kit") {
551
+ else if (manifestKitName !== CANONICAL_KIT_NAME &&
552
+ manifestKitName !== "quicktask-workspace-kit") {
563
553
  warnings.push(`${defaultWorkspaceKitPaths.manifest}: kit.name is '${manifestKitName}', skipping package-version drift comparison`);
564
554
  }
565
555
  }
@@ -581,129 +571,12 @@ export async function runCli(args, options = {}) {
581
571
  return EXIT_SUCCESS;
582
572
  }
583
573
  if (command === "run") {
584
- const allModules = [
585
- workspaceConfigModule,
586
- documentationModule,
587
- taskEngineModule,
588
- approvalsModule,
589
- planningModule,
590
- improvementModule
591
- ];
592
- const registry = new ModuleRegistry(allModules);
593
- const router = new ModuleCommandRouter(registry);
594
- const subcommand = args[1];
595
- if (!subcommand) {
596
- const commands = router.listCommands();
597
- writeLine("Available module commands:");
598
- for (const cmd of commands) {
599
- const desc = cmd.description ? ` — ${cmd.description}` : "";
600
- writeLine(` ${cmd.name} (${cmd.moduleId})${desc}`);
601
- }
602
- writeLine("");
603
- writeLine("Usage: workspace-kit run <command> [json-args]");
604
- return EXIT_SUCCESS;
605
- }
606
- let commandArgs = {};
607
- if (args[2]) {
608
- try {
609
- const parsed = JSON.parse(args[2]);
610
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
611
- commandArgs = parsed;
612
- }
613
- else {
614
- writeError("Command args must be a JSON object.");
615
- return EXIT_USAGE_ERROR;
616
- }
617
- }
618
- catch {
619
- writeError(`Invalid JSON args: ${args[2]}`);
620
- return EXIT_USAGE_ERROR;
621
- }
622
- }
623
- const invocationConfig = typeof commandArgs.config === "object" &&
624
- commandArgs.config !== null &&
625
- !Array.isArray(commandArgs.config)
626
- ? commandArgs.config
627
- : {};
628
- let effective;
629
- try {
630
- const resolved = await resolveWorkspaceConfigWithLayers({
631
- workspacePath: cwd,
632
- registry,
633
- invocationConfig
634
- });
635
- effective = resolved.effective;
636
- }
637
- catch (err) {
638
- const message = err instanceof Error ? err.message : String(err);
639
- writeError(`Config resolution failed: ${message}`);
640
- return EXIT_VALIDATION_FAILURE;
641
- }
642
- const actor = resolveActor(cwd, commandArgs, process.env);
643
- const sensitive = isSensitiveModuleCommandForEffective(subcommand, commandArgs, effective);
644
- const sessionId = resolveSessionId(process.env);
645
- const policyOp = resolvePolicyOperationIdForCommand(subcommand, effective);
646
- const explicitPolicyApproval = parsePolicyApproval(commandArgs);
647
- let resolvedSensitiveApproval = explicitPolicyApproval;
648
- if (sensitive) {
649
- if (!resolvedSensitiveApproval && policyOp) {
650
- const grant = await getSessionGrant(cwd, policyOp, sessionId);
651
- if (grant) {
652
- resolvedSensitiveApproval = { confirmed: true, rationale: grant.rationale };
653
- }
654
- }
655
- if (!resolvedSensitiveApproval) {
656
- if (policyOp) {
657
- await appendPolicyTrace(cwd, {
658
- timestamp: new Date().toISOString(),
659
- operationId: policyOp,
660
- command: `run ${subcommand}`,
661
- actor,
662
- allowed: false,
663
- message: "missing policyApproval in JSON args"
664
- });
665
- }
666
- writeLine(JSON.stringify({
667
- ok: false,
668
- code: "policy-denied",
669
- message: 'Sensitive command requires policyApproval in JSON args (or an existing session grant for this operation): {"policyApproval":{"confirmed":true,"rationale":"why","scope":"session"}}'
670
- }, null, 2));
671
- return EXIT_VALIDATION_FAILURE;
672
- }
673
- }
674
- const ctx = {
675
- runtimeVersion: "0.1",
676
- workspacePath: cwd,
677
- effectiveConfig: effective,
678
- resolvedActor: actor,
679
- moduleRegistry: registry
680
- };
681
- try {
682
- const rawResult = await router.execute(subcommand, commandArgs, ctx);
683
- if (sensitive && resolvedSensitiveApproval && policyOp) {
684
- await appendPolicyTrace(cwd, {
685
- timestamp: new Date().toISOString(),
686
- operationId: policyOp,
687
- command: `run ${subcommand}`,
688
- actor,
689
- allowed: true,
690
- rationale: resolvedSensitiveApproval.rationale,
691
- commandOk: rawResult.ok,
692
- message: rawResult.message
693
- });
694
- if (explicitPolicyApproval?.scope === "session" && rawResult.ok) {
695
- await recordSessionGrant(cwd, policyOp, sessionId, explicitPolicyApproval.rationale);
696
- }
697
- }
698
- const result = applyResponseTemplateApplication(subcommand, commandArgs, rawResult, effective);
699
- writeLine(JSON.stringify(result, null, 2));
700
- return result.ok ? EXIT_SUCCESS : EXIT_VALIDATION_FAILURE;
701
- }
702
- catch (error) {
703
- const message = error instanceof Error ? error.message : String(error);
704
- writeError(`Module command failed: ${message}`);
705
- return EXIT_INTERNAL_ERROR;
706
- }
574
+ return handleRunCommand(cwd, args, { writeLine, writeError }, {
575
+ success: EXIT_SUCCESS,
576
+ validationFailure: EXIT_VALIDATION_FAILURE,
577
+ usageError: EXIT_USAGE_ERROR,
578
+ internalError: EXIT_INTERNAL_ERROR
579
+ });
707
580
  }
708
581
  if (command !== "doctor") {
709
582
  writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check, run, config.`);
@@ -3,7 +3,7 @@ import { createInterface } from "node:readline/promises";
3
3
  import { stdin as processStdin, stdout as processStdout } from "node:process";
4
4
  import path from "node:path";
5
5
  import { ModuleRegistry } from "./module-registry.js";
6
- import { appendPolicyTrace, parsePolicyApprovalFromEnv, resolveActor } from "./policy.js";
6
+ import { appendPolicyTrace, parsePolicyApprovalFromEnv, resolveActorWithFallback } from "./policy.js";
7
7
  import { appendConfigMutation, summarizeForEvidence } from "./config-mutations.js";
8
8
  import { assertWritableKey, getConfigKeyMetadata, listConfigMetadata, validatePersistedConfigDocument, validateValueForMetadata } from "./config-metadata.js";
9
9
  import { explainConfigPath, getAtPath, getProjectConfigPath, getUserConfigFilePath, resolveWorkspaceConfigWithLayers, stableStringifyConfig } from "./workspace-kit-config.js";
@@ -109,12 +109,12 @@ function parseConfigArgs(argv) {
109
109
  async function requireConfigApproval(cwd, commandLabel, writeError) {
110
110
  const approval = parsePolicyApprovalFromEnv(process.env);
111
111
  if (!approval) {
112
- writeError(`${commandLabel} requires WORKSPACE_KIT_POLICY_APPROVAL with JSON {"confirmed":true,"rationale":"..."}.`);
112
+ writeError(`${commandLabel} (cli.config-mutate) requires WORKSPACE_KIT_POLICY_APPROVAL with JSON {"confirmed":true,"rationale":"..."}. See docs/maintainers/POLICY-APPROVAL.md.`);
113
113
  await appendPolicyTrace(cwd, {
114
114
  timestamp: new Date().toISOString(),
115
115
  operationId: "cli.config-mutate",
116
116
  command: commandLabel,
117
- actor: resolveActor(cwd, {}, process.env),
117
+ actor: await resolveActorWithFallback(cwd, {}, process.env),
118
118
  allowed: false,
119
119
  message: "missing WORKSPACE_KIT_POLICY_APPROVAL"
120
120
  });
@@ -303,7 +303,7 @@ export async function runWorkspaceConfigCli(cwd, argv, io) {
303
303
  const prevVal = getAtPath(before, key);
304
304
  const next = setDeep(before, key, parsed);
305
305
  validatePersistedConfigDocument(next, scope === "project" ? ".workspace-kit/config.json" : "user config");
306
- const actor = resolveActor(cwd, {}, process.env);
306
+ const actor = await resolveActorWithFallback(cwd, {}, process.env);
307
307
  try {
308
308
  await writeConfigFileAtomic(fp, next);
309
309
  await appendConfigMutation(cwd, {
@@ -380,7 +380,7 @@ export async function runWorkspaceConfigCli(cwd, argv, io) {
380
380
  const prevVal = getAtPath(before, key);
381
381
  const next = unsetDeep(before, key);
382
382
  validatePersistedConfigDocument(next, scope === "project" ? ".workspace-kit/config.json" : "user config");
383
- const actor = resolveActor(cwd, {}, process.env);
383
+ const actor = await resolveActorWithFallback(cwd, {}, process.env);
384
384
  try {
385
385
  if (Object.keys(next).length === 0) {
386
386
  await fs.rm(fp, { force: true });
@@ -32,8 +32,8 @@ const REGISTRY = {
32
32
  "improvement.transcripts.sourcePath": {
33
33
  key: "improvement.transcripts.sourcePath",
34
34
  type: "string",
35
- description: "Relative path to transcript JSONL source files for sync operations.",
36
- default: ".cursor/agent-transcripts",
35
+ description: "Optional relative path to transcript JSONL source. When empty, sync uses discoveryPaths (repo-relative, then Cursor global ~/.cursor/projects/<slug>/agent-transcripts).",
36
+ default: "",
37
37
  domainScope: "project",
38
38
  owningModule: "improvement",
39
39
  sensitive: false,
@@ -136,7 +136,7 @@ const REGISTRY = {
136
136
  "improvement.transcripts.discoveryPaths": {
137
137
  key: "improvement.transcripts.discoveryPaths",
138
138
  type: "array",
139
- description: "Ordered relative paths tried when improvement.transcripts.sourcePath is unset (first existing wins).",
139
+ description: "Ordered relative paths tried when improvement.transcripts.sourcePath is unset (first existing wins). After these, sync tries Cursor global ~/.cursor/projects/<slug>/agent-transcripts.",
140
140
  default: [],
141
141
  domainScope: "project",
142
142
  owningModule: "improvement",
@@ -1,7 +1,7 @@
1
1
  export { ModuleRegistry, ModuleRegistryError, validateModuleSet, type ModuleRegistryOptions } from "./module-registry.js";
2
2
  export { ModuleCommandRouter, ModuleCommandRouterError, type ModuleCommandDescriptor, type ModuleCommandRouterOptions } from "./module-command-router.js";
3
3
  export { buildBaseConfigLayers, deepMerge, envToConfigOverlay, explainConfigPath, getAtPath, getProjectConfigPath, getUserConfigFilePath, KIT_CONFIG_DEFAULTS, loadUserLayer, mergeConfigLayers, MODULE_CONFIG_CONTRIBUTIONS, normalizeConfigForExport, PROJECT_CONFIG_REL, resolveWorkspaceConfigWithLayers, stableStringifyConfig, type ConfigLayer, type ConfigLayerId, type EffectiveWorkspaceConfig, type ExplainConfigResult, type ResolveWorkspaceConfigOptions } from "./workspace-kit-config.js";
4
- export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand, type PolicyOperationId, type PolicyTraceRecord, type PolicyTraceRecordInput } from "./policy.js";
4
+ export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_APPROVAL_HUMAN_DOC, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand, type PolicyOperationId, type PolicyTraceRecord, type PolicyTraceRecordInput } from "./policy.js";
5
5
  export { getSessionGrant, loadSessionPolicyDocument, recordSessionGrant, resolveSessionId, SESSION_POLICY_SCHEMA_VERSION, type SessionPolicyDocument, type SessionPolicyGrant } from "./session-policy.js";
6
6
  export { parseTemplateDirectiveFromText } from "./instruction-template-mapper.js";
7
7
  export { RESPONSE_TEMPLATE_CONTRACT_VERSION, MAX_TEMPLATE_WARNING_LENGTH, truncateTemplateWarning, type ResponseTemplateDefinition, type ResponseTemplateEnforcementMode } from "./response-template-contract.js";
@@ -1,7 +1,7 @@
1
1
  export { ModuleRegistry, ModuleRegistryError, validateModuleSet } from "./module-registry.js";
2
2
  export { ModuleCommandRouter, ModuleCommandRouterError } from "./module-command-router.js";
3
3
  export { buildBaseConfigLayers, deepMerge, envToConfigOverlay, explainConfigPath, getAtPath, getProjectConfigPath, getUserConfigFilePath, KIT_CONFIG_DEFAULTS, loadUserLayer, mergeConfigLayers, MODULE_CONFIG_CONTRIBUTIONS, normalizeConfigForExport, PROJECT_CONFIG_REL, resolveWorkspaceConfigWithLayers, stableStringifyConfig } from "./workspace-kit-config.js";
4
- export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand } from "./policy.js";
4
+ export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_APPROVAL_HUMAN_DOC, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand } from "./policy.js";
5
5
  export { getSessionGrant, loadSessionPolicyDocument, recordSessionGrant, resolveSessionId, SESSION_POLICY_SCHEMA_VERSION } from "./session-policy.js";
6
6
  export { parseTemplateDirectiveFromText } from "./instruction-template-mapper.js";
7
7
  export { RESPONSE_TEMPLATE_CONTRACT_VERSION, MAX_TEMPLATE_WARNING_LENGTH, truncateTemplateWarning } from "./response-template-contract.js";
@@ -1,4 +1,6 @@
1
1
  export declare const POLICY_TRACE_SCHEMA_VERSION: 1;
2
+ /** Maintainer doc (repo-relative) linked from policy denial output for `workspace-kit run`. */
3
+ export declare const POLICY_APPROVAL_HUMAN_DOC = "docs/maintainers/POLICY-APPROVAL.md";
2
4
  export type PolicyOperationId = "cli.upgrade" | "cli.init" | "cli.config-mutate" | "policy.dynamic-sensitive" | "doc.document-project" | "doc.generate-document" | "tasks.run-transition" | "approvals.review-item" | "improvement.generate-recommendations" | "improvement.ingest-transcripts";
3
5
  export declare function getOperationIdForCommand(commandName: string): PolicyOperationId | undefined;
4
6
  export declare function getExtraSensitiveModuleCommandsFromEffective(effective: Record<string, unknown>): string[];
@@ -17,7 +19,15 @@ export type PolicyApprovalPayload = {
17
19
  };
18
20
  export declare function parsePolicyApprovalFromEnv(env: NodeJS.ProcessEnv): PolicyApprovalPayload | undefined;
19
21
  export declare function parsePolicyApproval(args: Record<string, unknown>): PolicyApprovalPayload | undefined;
20
- export declare function resolveActor(workspacePath: string, args: Record<string, unknown>, env: NodeJS.ProcessEnv): string;
22
+ export declare function resolveActor(_workspacePath: string, args: Record<string, unknown>, env: NodeJS.ProcessEnv): string;
23
+ /**
24
+ * Actor precedence:
25
+ * 1) command args.actor
26
+ * 2) WORKSPACE_KIT_ACTOR
27
+ * 3) bounded git user.email/user.name lookup (unless WORKSPACE_KIT_ACTOR_GIT_LOOKUP=off)
28
+ * 4) "unknown"
29
+ */
30
+ export declare function resolveActorWithFallback(workspacePath: string, args: Record<string, unknown>, env: NodeJS.ProcessEnv): Promise<string>;
21
31
  export type PolicyTraceRecord = {
22
32
  schemaVersion: number;
23
33
  timestamp: string;
@@ -1,7 +1,9 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { execSync } from "node:child_process";
3
+ import { execFile } from "node:child_process";
4
4
  export const POLICY_TRACE_SCHEMA_VERSION = 1;
5
+ /** Maintainer doc (repo-relative) linked from policy denial output for `workspace-kit run`. */
6
+ export const POLICY_APPROVAL_HUMAN_DOC = "docs/maintainers/POLICY-APPROVAL.md";
5
7
  const COMMAND_TO_OPERATION = {
6
8
  "document-project": "doc.document-project",
7
9
  "generate-document": "doc.generate-document",
@@ -83,37 +85,54 @@ export function parsePolicyApproval(args) {
83
85
  const scope = scopeRaw === "session" || scopeRaw === "once" ? scopeRaw : undefined;
84
86
  return { confirmed, rationale, ...(scope ? { scope } : {}) };
85
87
  }
86
- export function resolveActor(workspacePath, args, env) {
88
+ export function resolveActor(_workspacePath, args, env) {
87
89
  if (typeof args.actor === "string" && args.actor.trim().length > 0) {
88
90
  return args.actor.trim();
89
91
  }
90
92
  const fromEnv = env.WORKSPACE_KIT_ACTOR?.trim();
91
93
  if (fromEnv)
92
94
  return fromEnv;
93
- try {
94
- const email = execSync("git config user.email", {
95
- cwd: workspacePath,
96
- encoding: "utf8",
97
- stdio: ["ignore", "pipe", "ignore"]
98
- }).trim();
99
- if (email)
100
- return email;
101
- }
102
- catch {
103
- /* ignore */
104
- }
105
- try {
106
- const name = execSync("git config user.name", {
95
+ return "unknown";
96
+ }
97
+ function runGitConfigValue(workspacePath, key, timeoutMs) {
98
+ return new Promise((resolve) => {
99
+ execFile("git", ["config", key], {
107
100
  cwd: workspacePath,
108
101
  encoding: "utf8",
109
- stdio: ["ignore", "pipe", "ignore"]
110
- }).trim();
111
- if (name)
112
- return name;
102
+ timeout: timeoutMs,
103
+ windowsHide: true
104
+ }, (error, stdout) => {
105
+ if (error) {
106
+ resolve(undefined);
107
+ return;
108
+ }
109
+ const trimmed = stdout.trim();
110
+ resolve(trimmed.length > 0 ? trimmed : undefined);
111
+ });
112
+ });
113
+ }
114
+ /**
115
+ * Actor precedence:
116
+ * 1) command args.actor
117
+ * 2) WORKSPACE_KIT_ACTOR
118
+ * 3) bounded git user.email/user.name lookup (unless WORKSPACE_KIT_ACTOR_GIT_LOOKUP=off)
119
+ * 4) "unknown"
120
+ */
121
+ export async function resolveActorWithFallback(workspacePath, args, env) {
122
+ const explicit = resolveActor(workspacePath, args, env);
123
+ if (explicit !== "unknown") {
124
+ return explicit;
113
125
  }
114
- catch {
115
- /* ignore */
126
+ if (env.WORKSPACE_KIT_ACTOR_GIT_LOOKUP?.trim().toLowerCase() === "off") {
127
+ return "unknown";
116
128
  }
129
+ const timeoutMs = 300;
130
+ const email = await runGitConfigValue(workspacePath, "user.email", timeoutMs);
131
+ if (email)
132
+ return email;
133
+ const name = await runGitConfigValue(workspacePath, "user.name", timeoutMs);
134
+ if (name)
135
+ return name;
117
136
  return "unknown";
118
137
  }
119
138
  /** Policy sensitivity from built-in map plus `policy.extraSensitiveModuleCommands` on effective config. */