@workflow-cannon/workspace-kit 0.6.0 → 0.8.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 +3 -3
  2. package/dist/cli.js +31 -21
  3. package/dist/contracts/index.d.ts +1 -1
  4. package/dist/contracts/module-contract.d.ts +13 -0
  5. package/dist/core/config-metadata.js +303 -1
  6. package/dist/core/index.d.ts +6 -0
  7. package/dist/core/index.js +6 -0
  8. package/dist/core/instruction-template-mapper.d.ts +9 -0
  9. package/dist/core/instruction-template-mapper.js +35 -0
  10. package/dist/core/lineage-contract.d.ts +1 -1
  11. package/dist/core/lineage-contract.js +1 -1
  12. package/dist/core/policy.d.ts +4 -1
  13. package/dist/core/policy.js +5 -4
  14. package/dist/core/response-template-contract.d.ts +15 -0
  15. package/dist/core/response-template-contract.js +10 -0
  16. package/dist/core/response-template-registry.d.ts +4 -0
  17. package/dist/core/response-template-registry.js +44 -0
  18. package/dist/core/response-template-shaping.d.ts +6 -0
  19. package/dist/core/response-template-shaping.js +128 -0
  20. package/dist/core/session-policy.d.ts +18 -0
  21. package/dist/core/session-policy.js +57 -0
  22. package/dist/core/transcript-completion-hook.d.ts +7 -0
  23. package/dist/core/transcript-completion-hook.js +90 -0
  24. package/dist/core/workspace-kit-config.js +42 -2
  25. package/dist/modules/documentation/runtime.js +383 -14
  26. package/dist/modules/improvement/generate-recommendations-runtime.d.ts +7 -0
  27. package/dist/modules/improvement/generate-recommendations-runtime.js +51 -7
  28. package/dist/modules/improvement/improvement-state.d.ts +12 -1
  29. package/dist/modules/improvement/improvement-state.js +38 -7
  30. package/dist/modules/improvement/index.js +124 -2
  31. package/dist/modules/improvement/ingest.js +2 -1
  32. package/dist/modules/improvement/transcript-redaction.d.ts +4 -0
  33. package/dist/modules/improvement/transcript-redaction.js +10 -0
  34. package/dist/modules/improvement/transcript-sync-runtime.d.ts +60 -0
  35. package/dist/modules/improvement/transcript-sync-runtime.js +320 -0
  36. package/dist/modules/index.d.ts +1 -1
  37. package/dist/modules/index.js +1 -1
  38. package/dist/modules/task-engine/index.d.ts +0 -2
  39. package/dist/modules/task-engine/index.js +4 -70
  40. package/package.json +6 -2
  41. package/dist/modules/task-engine/generator.d.ts +0 -2
  42. package/dist/modules/task-engine/generator.js +0 -101
  43. package/dist/modules/task-engine/importer.d.ts +0 -8
  44. package/dist/modules/task-engine/importer.js +0 -157
@@ -0,0 +1,4 @@
1
+ import type { ResponseTemplateDefinition } from "./response-template-contract.js";
2
+ export declare function getResponseTemplateDefinition(id: string | undefined): ResponseTemplateDefinition | undefined;
3
+ export declare function listBuiltinResponseTemplateIds(): string[];
4
+ export declare function allBuiltinDefinitions(): ResponseTemplateDefinition[];
@@ -0,0 +1,44 @@
1
+ const BUILTIN = {
2
+ default: {
3
+ id: "default",
4
+ version: 1,
5
+ scope: "global",
6
+ description: "Passthrough with standard ok/code/message/data fields.",
7
+ expectedSections: ["ok", "code", "message", "data"]
8
+ },
9
+ compact: {
10
+ id: "compact",
11
+ version: 1,
12
+ scope: "global",
13
+ description: "Emphasizes top-level code and message for quick scanning.",
14
+ expectedSections: ["ok", "code", "message"]
15
+ },
16
+ completed_task: {
17
+ id: "completed_task",
18
+ version: 1,
19
+ scope: "command",
20
+ description: "Structured completion-style surface for task-style commands.",
21
+ expectedSections: ["ok", "code", "message", "data"]
22
+ },
23
+ COMPLETED_TASK: {
24
+ id: "COMPLETED_TASK",
25
+ version: 1,
26
+ scope: "command",
27
+ description: "Alias id matching plain-English directive spelling.",
28
+ expectedSections: ["ok", "code", "message", "data"]
29
+ }
30
+ };
31
+ export function getResponseTemplateDefinition(id) {
32
+ if (!id || typeof id !== "string")
33
+ return undefined;
34
+ const trimmed = id.trim();
35
+ if (!trimmed)
36
+ return undefined;
37
+ return BUILTIN[trimmed] ?? BUILTIN[trimmed.toLowerCase()] ?? undefined;
38
+ }
39
+ export function listBuiltinResponseTemplateIds() {
40
+ return Object.keys(BUILTIN).sort((a, b) => a.localeCompare(b));
41
+ }
42
+ export function allBuiltinDefinitions() {
43
+ return Object.values(BUILTIN);
44
+ }
@@ -0,0 +1,6 @@
1
+ import type { ModuleCommandResult } from "../contracts/module-contract.js";
2
+ /**
3
+ * Apply response template metadata and optional presentation hints (T262, T265).
4
+ * Advisory mode never flips `ok`. Strict mode fails closed on unknown template ids when a template was explicitly requested or command override is set.
5
+ */
6
+ export declare function applyResponseTemplateApplication(commandName: string, args: Record<string, unknown>, result: ModuleCommandResult, effective: Record<string, unknown>): ModuleCommandResult;
@@ -0,0 +1,128 @@
1
+ import { truncateTemplateWarning } from "./response-template-contract.js";
2
+ import { getResponseTemplateDefinition } from "./response-template-registry.js";
3
+ import { parseTemplateDirectiveFromText } from "./instruction-template-mapper.js";
4
+ function readResponseTemplatesConfig(effective) {
5
+ const rt = effective.responseTemplates;
6
+ if (!rt || typeof rt !== "object" || Array.isArray(rt)) {
7
+ return { enforcementMode: "advisory", defaultTemplateId: "default", commandOverrides: {} };
8
+ }
9
+ const o = rt;
10
+ const modeRaw = o.enforcementMode;
11
+ const enforcementMode = modeRaw === "strict" ? "strict" : "advisory";
12
+ const defRaw = o.defaultTemplateId;
13
+ const def = typeof defRaw === "string" && defRaw.trim().length > 0 ? defRaw.trim() : "default";
14
+ const commandOverrides = {};
15
+ const co = o.commandOverrides;
16
+ if (co && typeof co === "object" && !Array.isArray(co)) {
17
+ for (const [k, v] of Object.entries(co)) {
18
+ if (typeof v === "string" && v.trim()) {
19
+ commandOverrides[k.trim()] = v.trim();
20
+ }
21
+ }
22
+ }
23
+ return { enforcementMode, defaultTemplateId: def, commandOverrides };
24
+ }
25
+ function resolveRequestedTemplateId(commandName, args) {
26
+ const parseWarnings = [];
27
+ const explicit = typeof args.responseTemplateId === "string" && args.responseTemplateId.trim()
28
+ ? args.responseTemplateId.trim()
29
+ : null;
30
+ const directiveSources = [
31
+ typeof args.responseTemplateDirective === "string" ? args.responseTemplateDirective : "",
32
+ typeof args.instructionTemplateDirective === "string" ? args.instructionTemplateDirective : "",
33
+ typeof args.instruction === "string" ? args.instruction : ""
34
+ ].filter(Boolean);
35
+ let fromText = null;
36
+ for (const src of directiveSources) {
37
+ const parsed = parseTemplateDirectiveFromText(src);
38
+ parseWarnings.push(...parsed.warnings);
39
+ if (parsed.templateId) {
40
+ fromText = parsed.templateId;
41
+ break;
42
+ }
43
+ }
44
+ if (explicit && fromText && explicit !== fromText) {
45
+ parseWarnings.push(truncateTemplateWarning(`responseTemplateId '${explicit}' disagrees with instruction directive '${fromText}'; using explicit id.`));
46
+ }
47
+ if (explicit)
48
+ return { templateId: explicit, parseWarnings };
49
+ if (fromText)
50
+ return { templateId: fromText, parseWarnings };
51
+ return { templateId: null, parseWarnings };
52
+ }
53
+ function attachPresentation(templateId, result) {
54
+ const def = getResponseTemplateDefinition(templateId);
55
+ if (!def || !result.data || typeof result.data !== "object" || Array.isArray(result.data)) {
56
+ return undefined;
57
+ }
58
+ const data = result.data;
59
+ const matchedSections = def.expectedSections.filter((k) => k in data);
60
+ return {
61
+ templateId: def.id,
62
+ matchedSections
63
+ };
64
+ }
65
+ function buildMeta(partial, startNs) {
66
+ const warningCount = partial.warnings.length;
67
+ return {
68
+ ...partial,
69
+ telemetry: {
70
+ resolveNs: Number(process.hrtime.bigint() - startNs),
71
+ warningCount
72
+ }
73
+ };
74
+ }
75
+ /**
76
+ * Apply response template metadata and optional presentation hints (T262, T265).
77
+ * Advisory mode never flips `ok`. Strict mode fails closed on unknown template ids when a template was explicitly requested or command override is set.
78
+ */
79
+ export function applyResponseTemplateApplication(commandName, args, result, effective) {
80
+ const startNs = process.hrtime.bigint();
81
+ const cfg = readResponseTemplatesConfig(effective);
82
+ const { templateId: requestedRaw, parseWarnings } = resolveRequestedTemplateId(commandName, args);
83
+ const override = cfg.commandOverrides[commandName];
84
+ const chosenId = requestedRaw ?? override ?? cfg.defaultTemplateId ?? "default";
85
+ const warnings = [...parseWarnings];
86
+ const def = getResponseTemplateDefinition(chosenId);
87
+ if (!def) {
88
+ warnings.push(truncateTemplateWarning(`Unknown response template '${chosenId}'.`));
89
+ const explicitRequest = Boolean(requestedRaw || override);
90
+ if (cfg.enforcementMode === "strict" && explicitRequest) {
91
+ return {
92
+ ...result,
93
+ ok: false,
94
+ code: "response-template-invalid",
95
+ message: truncateTemplateWarning(`Unknown response template '${chosenId}'.`),
96
+ responseTemplate: buildMeta({
97
+ requestedTemplateId: requestedRaw ?? override,
98
+ appliedTemplateId: null,
99
+ enforcementMode: cfg.enforcementMode,
100
+ warnings
101
+ }, startNs)
102
+ };
103
+ }
104
+ return {
105
+ ...result,
106
+ responseTemplate: buildMeta({
107
+ requestedTemplateId: requestedRaw ?? override ?? cfg.defaultTemplateId,
108
+ appliedTemplateId: null,
109
+ enforcementMode: cfg.enforcementMode,
110
+ warnings
111
+ }, startNs)
112
+ };
113
+ }
114
+ const presentation = attachPresentation(def.id, result);
115
+ const nextData = presentation && result.data && typeof result.data === "object" && !Array.isArray(result.data)
116
+ ? { ...result.data, presentation }
117
+ : result.data;
118
+ return {
119
+ ...result,
120
+ data: nextData,
121
+ responseTemplate: buildMeta({
122
+ requestedTemplateId: requestedRaw ?? override ?? cfg.defaultTemplateId,
123
+ appliedTemplateId: def.id,
124
+ enforcementMode: cfg.enforcementMode,
125
+ warnings
126
+ }, startNs)
127
+ };
128
+ }
@@ -0,0 +1,18 @@
1
+ import type { PolicyOperationId } from "./policy.js";
2
+ export declare const SESSION_POLICY_SCHEMA_VERSION: 1;
3
+ export type SessionPolicyGrant = {
4
+ rationale: string;
5
+ grantedAt: string;
6
+ };
7
+ export type SessionPolicyDocument = {
8
+ schemaVersion: typeof SESSION_POLICY_SCHEMA_VERSION;
9
+ /** Logical session id (override with WORKSPACE_KIT_SESSION_ID). */
10
+ sessionId: string;
11
+ /** Active grants for sensitive operations within this session file. */
12
+ grants: Partial<Record<PolicyOperationId, SessionPolicyGrant>>;
13
+ };
14
+ export declare function resolveSessionId(env: NodeJS.ProcessEnv): string;
15
+ export declare function loadSessionPolicyDocument(workspacePath: string): Promise<SessionPolicyDocument>;
16
+ export declare function saveSessionPolicyDocument(workspacePath: string, doc: SessionPolicyDocument): Promise<void>;
17
+ export declare function getSessionGrant(workspacePath: string, operationId: PolicyOperationId, sessionId: string): Promise<SessionPolicyGrant | undefined>;
18
+ export declare function recordSessionGrant(workspacePath: string, operationId: PolicyOperationId, sessionId: string, rationale: string): Promise<void>;
@@ -0,0 +1,57 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const SESSION_POLICY_SCHEMA_VERSION = 1;
4
+ const REL = ".workspace-kit/policy/session-grants.json";
5
+ function defaultDoc(sessionId) {
6
+ return {
7
+ schemaVersion: SESSION_POLICY_SCHEMA_VERSION,
8
+ sessionId,
9
+ grants: {}
10
+ };
11
+ }
12
+ export function resolveSessionId(env) {
13
+ const raw = env.WORKSPACE_KIT_SESSION_ID?.trim();
14
+ return raw && raw.length > 0 ? raw : "default";
15
+ }
16
+ export async function loadSessionPolicyDocument(workspacePath) {
17
+ const fp = path.join(workspacePath, REL);
18
+ try {
19
+ const raw = await fs.readFile(fp, "utf8");
20
+ const doc = JSON.parse(raw);
21
+ if (doc.schemaVersion !== SESSION_POLICY_SCHEMA_VERSION) {
22
+ return defaultDoc(resolveSessionId(process.env));
23
+ }
24
+ return {
25
+ ...defaultDoc(doc.sessionId ?? resolveSessionId(process.env)),
26
+ ...doc,
27
+ grants: doc.grants && typeof doc.grants === "object" ? doc.grants : {}
28
+ };
29
+ }
30
+ catch (e) {
31
+ if (e.code === "ENOENT") {
32
+ return defaultDoc(resolveSessionId(process.env));
33
+ }
34
+ throw e;
35
+ }
36
+ }
37
+ export async function saveSessionPolicyDocument(workspacePath, doc) {
38
+ const fp = path.join(workspacePath, REL);
39
+ await fs.mkdir(path.dirname(fp), { recursive: true });
40
+ await fs.writeFile(fp, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
41
+ }
42
+ export async function getSessionGrant(workspacePath, operationId, sessionId) {
43
+ const doc = await loadSessionPolicyDocument(workspacePath);
44
+ if (doc.sessionId !== sessionId) {
45
+ return undefined;
46
+ }
47
+ return doc.grants[operationId];
48
+ }
49
+ export async function recordSessionGrant(workspacePath, operationId, sessionId, rationale) {
50
+ const doc = await loadSessionPolicyDocument(workspacePath);
51
+ doc.sessionId = sessionId;
52
+ doc.grants[operationId] = {
53
+ rationale,
54
+ grantedAt: new Date().toISOString()
55
+ };
56
+ await saveSessionPolicyDocument(workspacePath, doc);
57
+ }
@@ -0,0 +1,7 @@
1
+ export declare function readAfterTaskCompletedHook(effective: Record<string, unknown>): "off" | "sync" | "ingest";
2
+ export declare function resolveWorkspaceKitCli(workspacePath: string): string | null;
3
+ /**
4
+ * After a task reaches `completed`, optionally spawn sync/ingest without blocking the transition (T274).
5
+ * Ingest requires WORKSPACE_KIT_POLICY_APPROVAL in the parent env or falls back to sync.
6
+ */
7
+ export declare function maybeSpawnTranscriptHookAfterCompletion(workspacePath: string, effective: Record<string, unknown>): void;
@@ -0,0 +1,90 @@
1
+ import { existsSync, mkdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+ const LOCK_REL = ".workspace-kit/improvement/transcript-hook.lock";
5
+ const LOCK_STALE_MS = 120_000;
6
+ export function readAfterTaskCompletedHook(effective) {
7
+ const imp = effective.improvement;
8
+ if (!imp || typeof imp !== "object" || Array.isArray(imp))
9
+ return "off";
10
+ const hooks = imp.hooks;
11
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks))
12
+ return "off";
13
+ const v = hooks.afterTaskCompleted;
14
+ if (v === "sync" || v === "ingest")
15
+ return v;
16
+ return "off";
17
+ }
18
+ export function resolveWorkspaceKitCli(workspacePath) {
19
+ const candidates = [
20
+ path.join(workspacePath, "node_modules", "@workflow-cannon", "workspace-kit", "dist", "cli.js"),
21
+ path.join(workspacePath, "dist", "cli.js")
22
+ ];
23
+ for (const c of candidates) {
24
+ if (existsSync(c))
25
+ return c;
26
+ }
27
+ return null;
28
+ }
29
+ function lockPath(workspacePath) {
30
+ return path.join(workspacePath, LOCK_REL);
31
+ }
32
+ function hookLockBusy(workspacePath) {
33
+ const fp = lockPath(workspacePath);
34
+ try {
35
+ const st = statSync(fp);
36
+ return Date.now() - st.mtimeMs < LOCK_STALE_MS;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ /**
43
+ * After a task reaches `completed`, optionally spawn sync/ingest without blocking the transition (T274).
44
+ * Ingest requires WORKSPACE_KIT_POLICY_APPROVAL in the parent env or falls back to sync.
45
+ */
46
+ export function maybeSpawnTranscriptHookAfterCompletion(workspacePath, effective) {
47
+ const mode = readAfterTaskCompletedHook(effective);
48
+ if (mode === "off")
49
+ return;
50
+ if (hookLockBusy(workspacePath))
51
+ return;
52
+ let subcommand = "sync-transcripts";
53
+ if (mode === "ingest" && process.env.WORKSPACE_KIT_POLICY_APPROVAL?.trim()) {
54
+ subcommand = "ingest-transcripts";
55
+ }
56
+ const cli = resolveWorkspaceKitCli(workspacePath);
57
+ if (!cli)
58
+ return;
59
+ const fp = lockPath(workspacePath);
60
+ try {
61
+ mkdirSync(path.dirname(fp), { recursive: true });
62
+ writeFileSync(fp, `${JSON.stringify({ at: new Date().toISOString(), subcommand })}\n`, "utf8");
63
+ }
64
+ catch {
65
+ return;
66
+ }
67
+ const child = spawn(process.execPath, [cli, "run", subcommand, "{}"], {
68
+ cwd: workspacePath,
69
+ detached: true,
70
+ stdio: "ignore",
71
+ env: process.env
72
+ });
73
+ child.unref();
74
+ child.on("exit", () => {
75
+ try {
76
+ unlinkSync(fp);
77
+ }
78
+ catch {
79
+ /* ignore */
80
+ }
81
+ });
82
+ child.on("error", () => {
83
+ try {
84
+ unlinkSync(fp);
85
+ }
86
+ catch {
87
+ /* ignore */
88
+ }
89
+ });
90
+ }
@@ -13,7 +13,30 @@ export const KIT_CONFIG_DEFAULTS = {
13
13
  tasks: {
14
14
  storeRelativePath: ".workspace-kit/tasks/state.json"
15
15
  },
16
- documentation: {}
16
+ documentation: {},
17
+ responseTemplates: {
18
+ enforcementMode: "advisory",
19
+ defaultTemplateId: "default",
20
+ commandOverrides: {}
21
+ },
22
+ improvement: {
23
+ transcripts: {
24
+ sourcePath: ".cursor/agent-transcripts",
25
+ archivePath: "agent-transcripts",
26
+ maxFilesPerSync: 5000,
27
+ maxBytesPerFile: 50_000_000,
28
+ maxTotalScanBytes: 500_000_000,
29
+ discoveryPaths: []
30
+ },
31
+ cadence: {
32
+ minIntervalMinutes: 15,
33
+ skipIfNoNewTranscripts: true,
34
+ maxRecommendationCandidatesPerRun: 500
35
+ },
36
+ hooks: {
37
+ afterTaskCompleted: "off"
38
+ }
39
+ }
17
40
  };
18
41
  /**
19
42
  * Static module-level defaults keyed by module id (merged in registry startup order).
@@ -30,7 +53,24 @@ export const MODULE_CONFIG_CONTRIBUTIONS = {
30
53
  },
31
54
  approvals: {},
32
55
  planning: {},
33
- improvement: {}
56
+ improvement: {
57
+ transcripts: {
58
+ sourcePath: ".cursor/agent-transcripts",
59
+ archivePath: "agent-transcripts",
60
+ maxFilesPerSync: 5000,
61
+ maxBytesPerFile: 50_000_000,
62
+ maxTotalScanBytes: 500_000_000,
63
+ discoveryPaths: []
64
+ },
65
+ cadence: {
66
+ minIntervalMinutes: 15,
67
+ skipIfNoNewTranscripts: true,
68
+ maxRecommendationCandidatesPerRun: 500
69
+ },
70
+ hooks: {
71
+ afterTaskCompleted: "off"
72
+ }
73
+ }
34
74
  };
35
75
  export function deepMerge(target, source) {
36
76
  const out = { ...target };