@workflow-cannon/workspace-kit 0.7.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.
- package/README.md +3 -3
- package/dist/cli.js +31 -21
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +13 -0
- package/dist/core/config-metadata.js +197 -3
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/instruction-template-mapper.d.ts +9 -0
- package/dist/core/instruction-template-mapper.js +35 -0
- package/dist/core/lineage-contract.d.ts +1 -1
- package/dist/core/lineage-contract.js +1 -1
- package/dist/core/policy.d.ts +4 -1
- package/dist/core/policy.js +3 -3
- package/dist/core/response-template-contract.d.ts +15 -0
- package/dist/core/response-template-contract.js +10 -0
- package/dist/core/response-template-registry.d.ts +4 -0
- package/dist/core/response-template-registry.js +44 -0
- package/dist/core/response-template-shaping.d.ts +6 -0
- package/dist/core/response-template-shaping.js +128 -0
- package/dist/core/session-policy.d.ts +18 -0
- package/dist/core/session-policy.js +57 -0
- package/dist/core/transcript-completion-hook.d.ts +7 -0
- package/dist/core/transcript-completion-hook.js +90 -0
- package/dist/core/workspace-kit-config.js +25 -4
- package/dist/modules/documentation/runtime.js +383 -14
- package/dist/modules/improvement/generate-recommendations-runtime.d.ts +7 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +37 -4
- package/dist/modules/improvement/improvement-state.d.ts +10 -1
- package/dist/modules/improvement/improvement-state.js +36 -7
- package/dist/modules/improvement/index.js +55 -20
- package/dist/modules/improvement/ingest.js +2 -1
- package/dist/modules/improvement/transcript-redaction.d.ts +4 -0
- package/dist/modules/improvement/transcript-redaction.js +10 -0
- package/dist/modules/improvement/transcript-sync-runtime.d.ts +37 -1
- package/dist/modules/improvement/transcript-sync-runtime.js +198 -9
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/task-engine/index.d.ts +0 -2
- package/dist/modules/task-engine/index.js +4 -78
- package/package.json +5 -2
- package/dist/modules/task-engine/generator.d.ts +0 -3
- package/dist/modules/task-engine/generator.js +0 -118
- package/dist/modules/task-engine/importer.d.ts +0 -8
- package/dist/modules/task-engine/importer.js +0 -163
|
@@ -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
|
+
}
|
|
@@ -14,14 +14,27 @@ export const KIT_CONFIG_DEFAULTS = {
|
|
|
14
14
|
storeRelativePath: ".workspace-kit/tasks/state.json"
|
|
15
15
|
},
|
|
16
16
|
documentation: {},
|
|
17
|
+
responseTemplates: {
|
|
18
|
+
enforcementMode: "advisory",
|
|
19
|
+
defaultTemplateId: "default",
|
|
20
|
+
commandOverrides: {}
|
|
21
|
+
},
|
|
17
22
|
improvement: {
|
|
18
23
|
transcripts: {
|
|
19
24
|
sourcePath: ".cursor/agent-transcripts",
|
|
20
|
-
archivePath: "agent-transcripts"
|
|
25
|
+
archivePath: "agent-transcripts",
|
|
26
|
+
maxFilesPerSync: 5000,
|
|
27
|
+
maxBytesPerFile: 50_000_000,
|
|
28
|
+
maxTotalScanBytes: 500_000_000,
|
|
29
|
+
discoveryPaths: []
|
|
21
30
|
},
|
|
22
31
|
cadence: {
|
|
23
32
|
minIntervalMinutes: 15,
|
|
24
|
-
skipIfNoNewTranscripts: true
|
|
33
|
+
skipIfNoNewTranscripts: true,
|
|
34
|
+
maxRecommendationCandidatesPerRun: 500
|
|
35
|
+
},
|
|
36
|
+
hooks: {
|
|
37
|
+
afterTaskCompleted: "off"
|
|
25
38
|
}
|
|
26
39
|
}
|
|
27
40
|
};
|
|
@@ -43,11 +56,19 @@ export const MODULE_CONFIG_CONTRIBUTIONS = {
|
|
|
43
56
|
improvement: {
|
|
44
57
|
transcripts: {
|
|
45
58
|
sourcePath: ".cursor/agent-transcripts",
|
|
46
|
-
archivePath: "agent-transcripts"
|
|
59
|
+
archivePath: "agent-transcripts",
|
|
60
|
+
maxFilesPerSync: 5000,
|
|
61
|
+
maxBytesPerFile: 50_000_000,
|
|
62
|
+
maxTotalScanBytes: 500_000_000,
|
|
63
|
+
discoveryPaths: []
|
|
47
64
|
},
|
|
48
65
|
cadence: {
|
|
49
66
|
minIntervalMinutes: 15,
|
|
50
|
-
skipIfNoNewTranscripts: true
|
|
67
|
+
skipIfNoNewTranscripts: true,
|
|
68
|
+
maxRecommendationCandidatesPerRun: 500
|
|
69
|
+
},
|
|
70
|
+
hooks: {
|
|
71
|
+
afterTaskCompleted: "off"
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
74
|
};
|