@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.
- package/README.md +21 -5
- package/dist/cli/run-command.d.ts +11 -0
- package/dist/cli/run-command.js +143 -0
- package/dist/cli.js +19 -146
- package/dist/core/config-cli.js +5 -5
- package/dist/core/config-metadata.js +3 -3
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/policy.d.ts +11 -1
- package/dist/core/policy.js +41 -22
- package/dist/core/transcript-completion-hook.js +41 -3
- package/dist/core/workspace-kit-config.d.ts +2 -1
- package/dist/core/workspace-kit-config.js +4 -29
- package/dist/modules/approvals/index.js +2 -2
- package/dist/modules/documentation/runtime.js +30 -6
- package/dist/modules/improvement/index.js +15 -3
- package/dist/modules/improvement/transcript-sync-runtime.d.ts +5 -0
- package/dist/modules/improvement/transcript-sync-runtime.js +17 -0
- package/package.json +2 -1
- package/src/modules/documentation/README.md +39 -0
- package/src/modules/documentation/RULES.md +70 -0
- package/src/modules/documentation/config.md +14 -0
- package/src/modules/documentation/index.ts +120 -0
- package/src/modules/documentation/instructions/document-project.md +44 -0
- package/src/modules/documentation/instructions/documentation-maintainer.md +81 -0
- package/src/modules/documentation/instructions/generate-document.md +44 -0
- package/src/modules/documentation/runtime.ts +870 -0
- package/src/modules/documentation/schemas/documentation-schema.md +54 -0
- package/src/modules/documentation/state.md +8 -0
- package/src/modules/documentation/templates/AGENTS.md +84 -0
- package/src/modules/documentation/templates/ARCHITECTURE.md +71 -0
- package/src/modules/documentation/templates/PRINCIPLES.md +122 -0
- package/src/modules/documentation/templates/RELEASING.md +96 -0
- package/src/modules/documentation/templates/ROADMAP.md +131 -0
- package/src/modules/documentation/templates/SECURITY.md +53 -0
- package/src/modules/documentation/templates/SUPPORT.md +40 -0
- package/src/modules/documentation/templates/TERMS.md +61 -0
- package/src/modules/documentation/templates/runbooks/consumer-cadence.md +55 -0
- package/src/modules/documentation/templates/runbooks/parity-validation-flow.md +68 -0
- package/src/modules/documentation/templates/runbooks/release-channels.md +30 -0
- package/src/modules/documentation/templates/workbooks/phase2-config-policy-workbook.md +42 -0
- package/src/modules/documentation/templates/workbooks/task-engine-workbook.md +42 -0
- package/src/modules/documentation/templates/workbooks/transcript-automation-baseline.md +68 -0
- 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
|
-
|
|
65
|
-
|
|
66
|
-
- **
|
|
67
|
-
- **Phase
|
|
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
|
-
-
|
|
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 {
|
|
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 {
|
|
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:
|
|
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:
|
|
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 ===
|
|
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:
|
|
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" &&
|
|
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 !==
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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.`);
|
package/dist/core/config-cli.js
CHANGED
|
@@ -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,
|
|
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:
|
|
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 =
|
|
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 =
|
|
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: "
|
|
36
|
-
default: "
|
|
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",
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/core/index.js
CHANGED
|
@@ -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";
|
package/dist/core/policy.d.ts
CHANGED
|
@@ -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(
|
|
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;
|
package/dist/core/policy.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
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(
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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. */
|