@hongmaple0820/dsh-scale 0.0.0-stage → 0.1.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 +59 -2
- package/cordis.patch.yml +6 -0
- package/lib/cliExecutor.d.ts +19 -0
- package/lib/cliExecutor.js +88 -0
- package/lib/cliExecutor.js.map +1 -0
- package/lib/client.d.ts +11 -0
- package/lib/client.js +114 -0
- package/lib/client.js.map +1 -0
- package/lib/commandsBridge.d.ts +26 -0
- package/lib/commandsBridge.js +52 -0
- package/lib/commandsBridge.js.map +1 -0
- package/lib/cortexBridge.d.ts +17 -0
- package/lib/cortexBridge.js +34 -0
- package/lib/cortexBridge.js.map +1 -0
- package/lib/dshTypes.d.ts +99 -0
- package/lib/dshTypes.js +9 -0
- package/lib/dshTypes.js.map +1 -0
- package/lib/evidenceBridge.d.ts +19 -0
- package/lib/evidenceBridge.js +92 -0
- package/lib/evidenceBridge.js.map +1 -0
- package/lib/index.d.ts +33 -0
- package/lib/index.js +195 -0
- package/lib/index.js.map +1 -0
- package/lib/shieldBridge.d.ts +28 -0
- package/lib/shieldBridge.js +74 -0
- package/lib/shieldBridge.js.map +1 -0
- package/lib/skillsBridge.d.ts +18 -0
- package/lib/skillsBridge.js +123 -0
- package/lib/skillsBridge.js.map +1 -0
- package/package.json +57 -4
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// dsh-scale: Evidence Bridge — post-execute evidence recording
|
|
2
|
+
// Hooks into tools/post-execute and records into SCALE's RuntimeEvidenceLedger.
|
|
3
|
+
//
|
|
4
|
+
// Plan §6 T1.3: the ledger's sync record() is writeFileSync — running that on
|
|
5
|
+
// every tool call would block the DSH Electron host's event loop. So:
|
|
6
|
+
// - writes go through recordAsync() (fs/promises, added for this bridge)
|
|
7
|
+
// - writes are serialized through a promise chain (order preserved,
|
|
8
|
+
// unbounded concurrency impossible)
|
|
9
|
+
// - default mode 'failures-only': successful tool calls are not persisted
|
|
10
|
+
// until the write-volume impact is measured (plan U8); 'all' records both
|
|
11
|
+
import { RuntimeEvidenceLedger } from '@hongmaple0820/scale-engine';
|
|
12
|
+
// Per-projectDir ledgers — a DSH session may serve multiple workspaces
|
|
13
|
+
const ledgers = new Map();
|
|
14
|
+
const ledgerErrors = new Map();
|
|
15
|
+
let writeQueue = Promise.resolve();
|
|
16
|
+
/** Await all in-flight evidence writes (tests, graceful shutdown). */
|
|
17
|
+
export function pendingEvidenceWrites() {
|
|
18
|
+
return writeQueue;
|
|
19
|
+
}
|
|
20
|
+
/** Reset module state — plugin unload (ctx.effect disposer) and tests. */
|
|
21
|
+
export function resetEvidenceBridge() {
|
|
22
|
+
ledgers.clear();
|
|
23
|
+
ledgerErrors.clear();
|
|
24
|
+
writeQueue = Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
function getLedger(projectDir) {
|
|
27
|
+
if (ledgers.has(projectDir))
|
|
28
|
+
return ledgers.get(projectDir) ?? null;
|
|
29
|
+
if (ledgerErrors.has(projectDir))
|
|
30
|
+
return null;
|
|
31
|
+
let ledger;
|
|
32
|
+
try {
|
|
33
|
+
ledger = new RuntimeEvidenceLedger({ projectDir, createDirs: true });
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
ledgerErrors.set(projectDir, err instanceof Error ? err : new Error(String(err)));
|
|
37
|
+
ledger = null;
|
|
38
|
+
}
|
|
39
|
+
ledgers.set(projectDir, ledger);
|
|
40
|
+
return ledger;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Record a tool execution. Fire-and-forget: enqueues an async write and
|
|
44
|
+
* returns immediately — evidence must never block or fail the tool result.
|
|
45
|
+
*/
|
|
46
|
+
export function recordToolEvidence(params) {
|
|
47
|
+
const mode = params.mode ?? 'failures-only';
|
|
48
|
+
if (mode === 'failures-only' && !params.error)
|
|
49
|
+
return;
|
|
50
|
+
const ledger = getLedger(params.projectDir);
|
|
51
|
+
if (!ledger)
|
|
52
|
+
return;
|
|
53
|
+
const filePath = typeof params.args?.file_path === 'string' ? params.args.file_path : undefined;
|
|
54
|
+
const summary = params.error
|
|
55
|
+
? `Failed or blocked tool call: ${params.toolName}`
|
|
56
|
+
: `Executed: ${params.toolName}`;
|
|
57
|
+
const resultPreview = (() => {
|
|
58
|
+
if (params.result === null || params.result === undefined)
|
|
59
|
+
return '';
|
|
60
|
+
if (typeof params.result === 'object') {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.stringify(params.result).slice(0, 500);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return '[unserializable]';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return String(params.result).slice(0, 500);
|
|
69
|
+
})();
|
|
70
|
+
writeQueue = writeQueue
|
|
71
|
+
.then(() => ledger.recordAsync({
|
|
72
|
+
sessionId: params.sessionId,
|
|
73
|
+
kind: 'tool',
|
|
74
|
+
title: `DSH: ${params.toolName}`,
|
|
75
|
+
status: params.error ? 'failed' : 'passed',
|
|
76
|
+
command: params.toolName,
|
|
77
|
+
exitCode: params.error ? 2 : 0,
|
|
78
|
+
summary,
|
|
79
|
+
artifacts: filePath ? [filePath] : undefined,
|
|
80
|
+
metadata: {
|
|
81
|
+
args: params.args,
|
|
82
|
+
result: resultPreview,
|
|
83
|
+
source: 'dsh-scale',
|
|
84
|
+
mode,
|
|
85
|
+
},
|
|
86
|
+
}))
|
|
87
|
+
.then(() => undefined)
|
|
88
|
+
.catch(() => {
|
|
89
|
+
// Evidence recording is best-effort; never surface into the tool pipeline
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=evidenceBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidenceBridge.js","sourceRoot":"","sources":["../src/evidenceBridge.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Schema from 'schemastery';
|
|
2
|
+
import type { CordisContext } from './dshTypes.js';
|
|
3
|
+
import { type EvidenceMode } from './evidenceBridge.js';
|
|
4
|
+
export interface ScalePluginConfig {
|
|
5
|
+
projectDir?: string;
|
|
6
|
+
enableShield?: boolean;
|
|
7
|
+
enableCortex?: boolean;
|
|
8
|
+
enableEvidence?: boolean;
|
|
9
|
+
enableCommands?: boolean;
|
|
10
|
+
enableSkills?: boolean;
|
|
11
|
+
/** 'failures-only' (default): record failed/blocked calls only (plan U8); 'all': record every call. */
|
|
12
|
+
evidenceMode?: EvidenceMode;
|
|
13
|
+
}
|
|
14
|
+
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
15
|
+
projectDir: Schema<string, string>;
|
|
16
|
+
enableShield: Schema<boolean, boolean>;
|
|
17
|
+
enableCortex: Schema<boolean, boolean>;
|
|
18
|
+
enableEvidence: Schema<boolean, boolean>;
|
|
19
|
+
enableCommands: Schema<boolean, boolean>;
|
|
20
|
+
enableSkills: Schema<boolean, boolean>;
|
|
21
|
+
evidenceMode: Schema<"failures-only" | "all", "failures-only" | "all">;
|
|
22
|
+
}>, Schemastery.ObjectT<{
|
|
23
|
+
projectDir: Schema<string, string>;
|
|
24
|
+
enableShield: Schema<boolean, boolean>;
|
|
25
|
+
enableCortex: Schema<boolean, boolean>;
|
|
26
|
+
enableEvidence: Schema<boolean, boolean>;
|
|
27
|
+
enableCommands: Schema<boolean, boolean>;
|
|
28
|
+
enableSkills: Schema<boolean, boolean>;
|
|
29
|
+
evidenceMode: Schema<"failures-only" | "all", "failures-only" | "all">;
|
|
30
|
+
}>>;
|
|
31
|
+
export declare const name = "dsh-scale";
|
|
32
|
+
export declare const inject: string[];
|
|
33
|
+
export declare function apply(ctx: CordisContext, config?: Partial<ScalePluginConfig>): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// dsh-scale: SCALE Engine governance plugin for DSH
|
|
2
|
+
// -------------------------------------------
|
|
3
|
+
// Components:
|
|
4
|
+
// shieldBridge — tools/pre-execute waterfall (block→deny, warn→ask)
|
|
5
|
+
// cortexBridge — systemPrompt.context instinct injection
|
|
6
|
+
// evidenceBridge — tools/post-execute evidence recording (async, failures-first)
|
|
7
|
+
//
|
|
8
|
+
// Plan §6 T1: "all three bridges in one apply() with ctx.effect cleanup"
|
|
9
|
+
import Schema from 'schemastery';
|
|
10
|
+
import { resolveProjectDir, toShieldInput, evaluateShield } from './shieldBridge.js';
|
|
11
|
+
import { buildCortexContext } from './cortexBridge.js';
|
|
12
|
+
import { recordToolEvidence, resetEvidenceBridge, pendingEvidenceWrites, } from './evidenceBridge.js';
|
|
13
|
+
import { registerScaleCommands } from './commandsBridge.js';
|
|
14
|
+
import { createScaleSkillProvider } from './skillsBridge.js';
|
|
15
|
+
// Cordis validates this export during plugin load (schemastery Schema — a
|
|
16
|
+
// plain object fails with "Cannot read properties of undefined (reading 'validate')")
|
|
17
|
+
export const Config = Schema.object({
|
|
18
|
+
projectDir: Schema.string().default(''),
|
|
19
|
+
enableShield: Schema.boolean().default(true),
|
|
20
|
+
enableCortex: Schema.boolean().default(true),
|
|
21
|
+
enableEvidence: Schema.boolean().default(true),
|
|
22
|
+
enableCommands: Schema.boolean().default(true),
|
|
23
|
+
enableSkills: Schema.boolean().default(true),
|
|
24
|
+
evidenceMode: Schema.union(['failures-only', 'all']).default('failures-only'),
|
|
25
|
+
});
|
|
26
|
+
export const name = 'dsh-scale';
|
|
27
|
+
// Required services (plan §2.3 / tutorial pitfall 2): accessing a ctx service
|
|
28
|
+
// without declaring it here throws at apply() time.
|
|
29
|
+
export const inject = ['tools', 'systemPrompt', 'commands', 'skills'];
|
|
30
|
+
/**
|
|
31
|
+
* tools/post-execute argument normalizer. The exact event arity was only
|
|
32
|
+
* runtime-verified for pre-execute (plan §5.6); handle both the documented
|
|
33
|
+
* (exec, result, next) shape and the single wrapped-object shape.
|
|
34
|
+
*/
|
|
35
|
+
function normalizePostExecute(args) {
|
|
36
|
+
const first = args[0];
|
|
37
|
+
if (args.length >= 2 && first !== null && typeof first === 'object' && 'name' in first) {
|
|
38
|
+
const result = args[1];
|
|
39
|
+
return {
|
|
40
|
+
exec: first,
|
|
41
|
+
isError: Boolean(result && typeof result === 'object' && 'isError' in result && result.isError),
|
|
42
|
+
result,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (first !== null && typeof first === 'object' && 'execution' in first) {
|
|
46
|
+
const wrapped = first;
|
|
47
|
+
return { exec: wrapped.execution ?? {}, isError: Boolean(wrapped.error), result: wrapped.result };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Required by Cordis: the one hook DSH calls for every plugin
|
|
52
|
+
export function apply(ctx, config = {}) {
|
|
53
|
+
const effective = {
|
|
54
|
+
enableShield: true,
|
|
55
|
+
enableCortex: true,
|
|
56
|
+
enableEvidence: true,
|
|
57
|
+
enableCommands: true,
|
|
58
|
+
enableSkills: true,
|
|
59
|
+
evidenceMode: 'failures-only',
|
|
60
|
+
...config,
|
|
61
|
+
};
|
|
62
|
+
// =========================================================
|
|
63
|
+
// Shield: tools/pre-execute waterfall (T1.1)
|
|
64
|
+
// =========================================================
|
|
65
|
+
if (effective.enableShield) {
|
|
66
|
+
ctx.on('tools/pre-execute', async (...args) => {
|
|
67
|
+
const exec = args[0];
|
|
68
|
+
const next = args[args.length - 1];
|
|
69
|
+
// MUST call next() — waterfall: if a prior hook already denied, skip
|
|
70
|
+
const decision = await next();
|
|
71
|
+
if (decision && decision.kind !== 'allow')
|
|
72
|
+
return decision;
|
|
73
|
+
const projectDir = resolveProjectDir(effective, exec);
|
|
74
|
+
const input = toShieldInput({ name: exec.name ?? 'unknown', arguments: exec.arguments, agent: exec.agent }, projectDir);
|
|
75
|
+
return evaluateShield(input);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// =========================================================
|
|
79
|
+
// Cortex: system prompt instinct injection (T1.2)
|
|
80
|
+
// =========================================================
|
|
81
|
+
if (effective.enableCortex) {
|
|
82
|
+
const projectDir = effective.projectDir || process.cwd();
|
|
83
|
+
const entry = buildCortexContext(projectDir);
|
|
84
|
+
// Single registration; DSH assembles the context into every model turn
|
|
85
|
+
if (entry && ctx.systemPrompt?.context) {
|
|
86
|
+
try {
|
|
87
|
+
ctx.systemPrompt.context(entry.id, entry.content, { order: entry.order });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// context registration is best-effort; Shield/Evidence stay up
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// =========================================================
|
|
95
|
+
// Evidence: tools/post-execute recording (T1.3)
|
|
96
|
+
// =========================================================
|
|
97
|
+
if (effective.enableEvidence) {
|
|
98
|
+
const fallbackProjectDir = effective.projectDir || process.cwd();
|
|
99
|
+
ctx.on('tools/post-execute', async (...args) => {
|
|
100
|
+
// waterfall: forward first (must not short-circuit the chain), then observe
|
|
101
|
+
const next = args[args.length - 1];
|
|
102
|
+
const decision = typeof next === 'function' ? await next() : undefined;
|
|
103
|
+
const normalized = normalizePostExecute(args);
|
|
104
|
+
if (normalized) {
|
|
105
|
+
const projectDir = resolveProjectDir(effective, normalized.exec) || fallbackProjectDir;
|
|
106
|
+
recordToolEvidence({
|
|
107
|
+
toolName: normalized.exec.name ?? 'unknown',
|
|
108
|
+
args: normalized.exec.arguments ?? {},
|
|
109
|
+
result: normalized.result,
|
|
110
|
+
error: normalized.isError,
|
|
111
|
+
projectDir,
|
|
112
|
+
sessionId: normalized.exec.agent?.id,
|
|
113
|
+
mode: effective.evidenceMode,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return decision;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// =========================================================
|
|
120
|
+
// Commands: governance gates as slash commands (T2.1)
|
|
121
|
+
// =========================================================
|
|
122
|
+
if (effective.enableCommands) {
|
|
123
|
+
const registered = registerScaleCommands(ctx, effective);
|
|
124
|
+
if (registered > 0) {
|
|
125
|
+
process.stderr.write(`[dsh-scale] registered ${registered} slash commands (scale-*)\n`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// =========================================================
|
|
129
|
+
// Skills: .scale/skills phase workflows into the DSH registry (T2.2)
|
|
130
|
+
// =========================================================
|
|
131
|
+
if (effective.enableSkills) {
|
|
132
|
+
const projectDirFor = () => effective.projectDir || process.cwd();
|
|
133
|
+
try {
|
|
134
|
+
ctx.skills?.registerProvider?.(() => createScaleSkillProvider(projectDirFor));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// duplicate provider name (reload) — keep the first registration
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// =========================================================
|
|
141
|
+
// Cleanup (plan §2.3 — dispose everything explicitly registered here;
|
|
142
|
+
// ctx.on listeners are tracked by Cordis itself)
|
|
143
|
+
// =========================================================
|
|
144
|
+
ctx.effect(() => {
|
|
145
|
+
return () => {
|
|
146
|
+
// Drain in-flight evidence writes, then reset bridge state so a later
|
|
147
|
+
// reload starts clean. Multiple async disposers have no ordering
|
|
148
|
+
// guarantee (plan §2.3) — this plugin has exactly one.
|
|
149
|
+
void pendingEvidenceWrites().finally(() => resetEvidenceBridge());
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
// =========================================================
|
|
153
|
+
// Self-test driver (§5.6 pattern — zero API cost, verifiable).
|
|
154
|
+
// OPT-IN: SCALE_DSH_SELFTEST=1 — never drive probe tool calls in
|
|
155
|
+
// production sessions.
|
|
156
|
+
// =========================================================
|
|
157
|
+
if (process.env.SCALE_DSH_SELFTEST === '1') {
|
|
158
|
+
// §2.12 fatal rule: any throw from a timer callback disposes the whole
|
|
159
|
+
// DSH app — the self-test driver must be exception-safe by contract.
|
|
160
|
+
setTimeout(async () => {
|
|
161
|
+
try {
|
|
162
|
+
const tools = ctx.tools;
|
|
163
|
+
if (!tools?.execute) {
|
|
164
|
+
process.stderr.write('[dsh-scale selftest] ctx.tools.execute unavailable; skipping\n');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
process.stderr.write('[dsh-scale selftest] apply() running; shieldBridge loaded\n\n');
|
|
168
|
+
// --- BLOCK: pwsh with recursive force delete (POSIX form — `rm` is a
|
|
169
|
+
// pwsh alias for Remove-Item; the blocklist matches /\brm\s+-rf\b/) ---
|
|
170
|
+
process.stderr.write('[selftest] driving BLOCK\n');
|
|
171
|
+
const blockRes = await tools.execute({ name: 'pwsh', arguments: { command: 'rm -rf /tmp/x' }, signal: new AbortController().signal });
|
|
172
|
+
process.stderr.write(`[selftest] RESULT isError=${blockRes.isError}\n`);
|
|
173
|
+
process.stderr.write(`[selftest] CONTENT ${JSON.stringify(blockRes.content)}\n`);
|
|
174
|
+
// --- BLOCK PROTECTED PATH: write to .scale/policy.yaml ---
|
|
175
|
+
process.stderr.write('[selftest] driving PROTECT\n');
|
|
176
|
+
const protRes = await tools.execute({ name: 'write', arguments: { file_path: '.scale/policy.yaml', content: 'test' }, signal: new AbortController().signal });
|
|
177
|
+
process.stderr.write(`[selftest] RESULT isError=${protRes.isError}\n`);
|
|
178
|
+
process.stderr.write(`[selftest] CONTENT ${JSON.stringify(protRes.content)}\n`);
|
|
179
|
+
// --- ALLOW: harmless pwsh call (no false positives) ---
|
|
180
|
+
// `read` is agent-scoped in dsh-base and invisible to a contextless
|
|
181
|
+
// boot-time probe ("unknown tool"); pwsh is globally registered.
|
|
182
|
+
process.stderr.write('[selftest] driving ALLOW (pwsh Get-Location)\n');
|
|
183
|
+
const allowRes = await tools.execute({ name: 'pwsh', arguments: { command: 'Get-Location' }, signal: new AbortController().signal });
|
|
184
|
+
process.stderr.write(`[selftest] RESULT isError=${allowRes.isError}\n`);
|
|
185
|
+
process.stderr.write(`[selftest] CONTENT ${JSON.stringify(allowRes.content).slice(0, 300)}\n`);
|
|
186
|
+
// Drain evidence writes so blocked calls land before process exit
|
|
187
|
+
await pendingEvidenceWrites();
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
process.stderr.write(`[dsh-scale selftest] FAILED: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
191
|
+
}
|
|
192
|
+
}, 500);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ShieldInput } from '@hongmaple0820/scale-engine';
|
|
2
|
+
export declare function resolveProjectDir(config: {
|
|
3
|
+
projectDir?: string;
|
|
4
|
+
}, exec: {
|
|
5
|
+
agent?: {
|
|
6
|
+
session?: {
|
|
7
|
+
header?: {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function toShieldInput(exec: {
|
|
14
|
+
name: string;
|
|
15
|
+
arguments?: Record<string, unknown>;
|
|
16
|
+
agent?: {
|
|
17
|
+
id?: string;
|
|
18
|
+
};
|
|
19
|
+
}, projectDir: string): ShieldInput;
|
|
20
|
+
export interface PreToolDecision {
|
|
21
|
+
kind: 'allow' | 'deny' | 'cancel' | 'ask';
|
|
22
|
+
reason?: string;
|
|
23
|
+
displayReason?: {
|
|
24
|
+
en: string;
|
|
25
|
+
[loc: string]: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export declare function evaluateShield(input: ShieldInput): PreToolDecision;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// dsh-scale: Shield Bridge — tool interception adapter
|
|
2
|
+
// Maps DSH tool names → SCALE ShieldInput, calls checkToolInput,
|
|
3
|
+
// and maps block/warn → deny/ask.
|
|
4
|
+
//
|
|
5
|
+
// Key properties (plan §6 T1.1):
|
|
6
|
+
// - MUST call next() in waterfall; skip when decision != allow
|
|
7
|
+
// - block → { kind: 'deny' }
|
|
8
|
+
// - warn → { kind: 'ask' } (not silently dropped as in bare hook mode)
|
|
9
|
+
// - mcp__scale__ prefix unpacking (shell_run → Bash semantic)
|
|
10
|
+
// - projectDir per-execution, not fixed at apply time
|
|
11
|
+
import { checkToolInput, checkWarnHits, } from '@hongmaple0820/scale-engine';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Tool name mapping (plan §6 — empirically verified against DSH's 107 tools)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const TOOL_NAME_MAP = {
|
|
16
|
+
pwsh: 'Bash',
|
|
17
|
+
write: 'Write',
|
|
18
|
+
edit: 'Edit',
|
|
19
|
+
read: 'Read',
|
|
20
|
+
};
|
|
21
|
+
const MCP_PREFIX = 'mcp__scale__';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// projectDir resolution — per-execution, not at apply time
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export function resolveProjectDir(config, exec) {
|
|
26
|
+
if (config.projectDir)
|
|
27
|
+
return config.projectDir;
|
|
28
|
+
if (exec.agent?.session?.header?.cwd)
|
|
29
|
+
return exec.agent.session.header.cwd;
|
|
30
|
+
return process.cwd();
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Shield input adapter
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export function toShieldInput(exec, projectDir) {
|
|
36
|
+
let toolName = exec.name;
|
|
37
|
+
// Unpack mcp__scale__<tool> prefix (plan §6 T1.1)
|
|
38
|
+
if (toolName.startsWith(MCP_PREFIX)) {
|
|
39
|
+
const raw = toolName.slice(MCP_PREFIX.length);
|
|
40
|
+
// shell_run has pwsh semantics for command blocklist matching
|
|
41
|
+
toolName = raw === 'shell_run' ? 'Bash' : raw;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
toolName = TOOL_NAME_MAP[toolName] ?? toolName;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
session_id: exec.agent?.id ?? 'unknown',
|
|
48
|
+
cwd: projectDir,
|
|
49
|
+
tool_name: toolName,
|
|
50
|
+
tool_input: exec.arguments ?? {},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function evaluateShield(input) {
|
|
54
|
+
const result = checkToolInput(input);
|
|
55
|
+
if (result.decision === 'block') {
|
|
56
|
+
return { kind: 'deny', reason: result.reason };
|
|
57
|
+
}
|
|
58
|
+
// warn → ask (not silently dropped — plan §6 audit gap fix)
|
|
59
|
+
if (input.tool_name === 'Bash') {
|
|
60
|
+
const command = String(input.tool_input?.command ?? '');
|
|
61
|
+
const warnHits = checkWarnHits(command);
|
|
62
|
+
if (warnHits.length > 0) {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'ask',
|
|
65
|
+
displayReason: {
|
|
66
|
+
en: `[SCALE SHIELD WARN] ${warnHits[0].reason} — ${warnHits[0].category}`,
|
|
67
|
+
zh: `【SCALE 盾警告】${warnHits[0].reason} — ${warnHits[0].category}`,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { kind: 'allow' };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=shieldBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shieldBridge.js","sourceRoot":"","sources":["../src/shieldBridge.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,iEAAiE;AACjE,kCAAkC;AAClC,EAAE;AACF,iCAAiC;AACjC,+DAA+D;AAC/D,6BAA6B;AAC7B,uEAAuE;AACvE,8DAA8D;AAC9D,sDAAsD;AAEtD,OAAO,EACL,cAAc,EACd,aAAa,GACd,MAAM,6BAA6B,CAAA;AAGpC,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,aAAa,GAA2B;IAC5C,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;CACb,CAAA;AAED,MAAM,UAAU,GAAG,cAAc,CAAA;AAEjC,8EAA8E;AAC9E,2DAA2D;AAC3D,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB,CAC/B,MAA+B,EAC/B,IAA6D;IAE7D,IAAI,MAAM,CAAC,UAAU;QAAE,OAAO,MAAM,CAAC,UAAU,CAAA;IAC/C,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAA;IAC1E,OAAO,OAAO,CAAC,GAAG,EAAE,CAAA;AACtB,CAAC;AAED,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAC3B,IAAoF,EACpF,UAAkB;IAElB,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAExB,kDAAkD;IAClD,IAAI,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAC7C,8DAA8D;QAC9D,QAAQ,GAAG,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAA;IAC/C,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAA;IAChD,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,SAAS;QACvC,GAAG,EAAE,UAAU;QACf,SAAS,EAAE,QAAQ;QACnB,UAAU,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;KACjC,CAAA;AACH,CAAC;AAWD,MAAM,UAAU,cAAc,CAC5B,KAAkB;IAElB,MAAM,MAAM,GAAmB,cAAc,CAAC,KAAK,CAAC,CAAA;IAEpD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAA;IAChD,CAAC;IAED,4DAA4D;IAC5D,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO;gBACL,IAAI,EAAE,KAAK;gBACX,aAAa,EAAE;oBACb,EAAE,EAAE,uBAAuB,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;oBACzE,EAAE,EAAE,cAAc,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;iBACjE;aACF,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC1B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SkillProvider } from './dshTypes.js';
|
|
2
|
+
export declare const SCALE_SKILL_PROVIDER = "scale-engine";
|
|
3
|
+
export interface ScaleSkillEntry {
|
|
4
|
+
/** kebab-case skill name (from the `# skill:` header, falling back to filename) */
|
|
5
|
+
name: string;
|
|
6
|
+
/** phase directory (DEFINE/BUILD/VERIFY/…) */
|
|
7
|
+
phase: string;
|
|
8
|
+
description: string;
|
|
9
|
+
whenToUse?: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
/** Scan .scale/skills/ phase directories for skill markdown files (sync — called once per list()). */
|
|
13
|
+
export declare function scanScaleSkills(projectDir: string): ScaleSkillEntry[];
|
|
14
|
+
/**
|
|
15
|
+
* Build the DSH skill provider for a project. create() itself is synchronous
|
|
16
|
+
* (contract); the disk scan runs inside list().
|
|
17
|
+
*/
|
|
18
|
+
export declare function createScaleSkillProvider(projectDir: () => string): SkillProvider;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// dsh-scale: Skills Bridge (T2.2) — exposes SCALE's phase-workflow skills
|
|
2
|
+
// (.scale/skills/<PHASE>/<name>.md) to DSH's skills registry through a
|
|
3
|
+
// provider. Registration is synchronous (plan §6 T2.2 / dsh-skill contract);
|
|
4
|
+
// body loading happens lazily in get().
|
|
5
|
+
//
|
|
6
|
+
// Deliberately NOT registered: skills.json domain routing metadata — those
|
|
7
|
+
// entries reference external skills from global roots (~/.agents/skills …)
|
|
8
|
+
// that DSH's own skill-filesystem discovery owns; duplicating them here
|
|
9
|
+
// would shadow or double-list third-party skills.
|
|
10
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
export const SCALE_SKILL_PROVIDER = 'scale-engine';
|
|
13
|
+
/** Mid-range: project entries outrank runtime, which outrank user entries (dsh-skill ranks). */
|
|
14
|
+
const SCALE_SKILL_RANK = 100;
|
|
15
|
+
function parseSkillMarkdown(text, fallbackName) {
|
|
16
|
+
const nameMatch = text.match(/^#\s+skill:\s*(.+)$/m);
|
|
17
|
+
const purposeMatch = text.match(/^##\s+Purpose\s*$/m);
|
|
18
|
+
const description = purposeMatch
|
|
19
|
+
? (() => {
|
|
20
|
+
const after = text.slice((purposeMatch.index ?? 0) + purposeMatch[0].length);
|
|
21
|
+
const nextSection = after.match(/^##\s/m);
|
|
22
|
+
return (nextSection ? after.slice(0, nextSection.index) : after).trim().split('\n')[0] ?? '';
|
|
23
|
+
})()
|
|
24
|
+
: '';
|
|
25
|
+
const triggerMatch = text.match(/^##\s+Triggers\s*$/m);
|
|
26
|
+
let whenToUse;
|
|
27
|
+
if (triggerMatch) {
|
|
28
|
+
const after = text.slice((triggerMatch.index ?? 0) + triggerMatch[0].length);
|
|
29
|
+
const nextSection = after.match(/^##\s/m);
|
|
30
|
+
const triggers = (nextSection ? after.slice(0, nextSection.index) : after)
|
|
31
|
+
.split('\n').map(l => l.replace(/^[-*]\s*/, '').trim()).filter(Boolean);
|
|
32
|
+
if (triggers.length > 0)
|
|
33
|
+
whenToUse = triggers.join('; ').slice(0, 200);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
name: (nameMatch?.[1] ?? fallbackName).trim().toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, ''),
|
|
37
|
+
description: description || `SCALE ${fallbackName} phase workflow skill`,
|
|
38
|
+
whenToUse,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
42
|
+
/** Scan .scale/skills/ phase directories for skill markdown files (sync — called once per list()). */
|
|
43
|
+
export function scanScaleSkills(projectDir) {
|
|
44
|
+
const root = join(projectDir, '.scale', 'skills');
|
|
45
|
+
let phases;
|
|
46
|
+
try {
|
|
47
|
+
phases = readdirSync(root, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const entries = [];
|
|
53
|
+
for (const phase of phases) {
|
|
54
|
+
let files;
|
|
55
|
+
try {
|
|
56
|
+
files = readdirSync(join(root, phase)).filter(f => f.endsWith('.md') && f !== '_INDEX.md');
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const relPath = `.scale/skills/${phase}/${file}`;
|
|
63
|
+
let text = '';
|
|
64
|
+
try {
|
|
65
|
+
text = readFileSync(join(root, phase, file), 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const fallback = file.replace(/\.md$/, '');
|
|
71
|
+
const parsed = parseSkillMarkdown(text, fallback);
|
|
72
|
+
if (!KEBAB.test(parsed.name))
|
|
73
|
+
continue;
|
|
74
|
+
entries.push({
|
|
75
|
+
name: parsed.name,
|
|
76
|
+
phase,
|
|
77
|
+
description: parsed.description,
|
|
78
|
+
whenToUse: parsed.whenToUse,
|
|
79
|
+
path: relPath,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
function toCandidate(entry) {
|
|
86
|
+
return {
|
|
87
|
+
name: entry.name,
|
|
88
|
+
description: entry.description,
|
|
89
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
90
|
+
source: entry.path,
|
|
91
|
+
provider: SCALE_SKILL_PROVIDER,
|
|
92
|
+
rank: SCALE_SKILL_RANK,
|
|
93
|
+
...(entry.whenToUse ? { whenToUse: entry.whenToUse } : {}),
|
|
94
|
+
path: entry.path,
|
|
95
|
+
locator: entry,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build the DSH skill provider for a project. create() itself is synchronous
|
|
100
|
+
* (contract); the disk scan runs inside list().
|
|
101
|
+
*/
|
|
102
|
+
export function createScaleSkillProvider(projectDir) {
|
|
103
|
+
return {
|
|
104
|
+
name: SCALE_SKILL_PROVIDER,
|
|
105
|
+
async list() {
|
|
106
|
+
return scanScaleSkills(projectDir()).map(toCandidate);
|
|
107
|
+
},
|
|
108
|
+
async get(candidate) {
|
|
109
|
+
const entry = candidate.locator;
|
|
110
|
+
const relPath = entry?.path ?? (typeof candidate.path === 'string' ? candidate.path : null);
|
|
111
|
+
if (!relPath)
|
|
112
|
+
return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const content = readFileSync(join(projectDir(), relPath), 'utf-8');
|
|
115
|
+
return { ...candidate, content };
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=skillsBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillsBridge.js","sourceRoot":"","sources":["../src/skillsBridge.ts"],"names":[],"mappings":"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"}
|