@opengsd/gsd-pi 1.1.1-dev.3ea310e → 1.1.1-dev.74e8dd1
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/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +4 -3
- package/dist/resources/extensions/gsd/auto-dashboard.js +15 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +111 -5
- package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
- package/dist/resources/extensions/gsd/auto-start.js +41 -12
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +2 -1
- package/dist/resources/extensions/gsd/auto.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +79 -0
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +43 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +30 -9
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +16 -10
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -1
- package/dist/resources/extensions/gsd/commands-verdict.js +1 -1
- package/dist/resources/extensions/gsd/config-overlay.js +2 -1
- package/dist/resources/extensions/gsd/error-classifier.js +2 -1
- package/dist/resources/extensions/gsd/exec-sandbox.js +2 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/dist/resources/extensions/gsd/prompts/system.md +3 -1
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +3 -0
- package/dist/resources/extensions/gsd/skill-activation.js +20 -3
- package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +18 -1
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +6 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/tools/exec-tool.js +109 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +366 -3
- package/dist/resources/extensions/gsd/unit-context-manifest.js +8 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +2 -0
- package/dist/resources/extensions/gsd/workflow-mcp-auto-prep.js +1 -1
- package/dist/resources/extensions/gsd/workflow-mcp.js +5 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +2 -2
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +14 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +16 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +10 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +69 -31
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-settings.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +82 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/image-models.generated.d.ts +15 -0
- package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/image-models.generated.js +15 -0
- package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +35 -1
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +53 -19
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/dist/terminal.d.ts +1 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +8 -4
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +5 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +16 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +136 -5
- package/src/resources/extensions/gsd/auto-prompts.ts +9 -0
- package/src/resources/extensions/gsd/auto-start.ts +54 -14
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +2 -1
- package/src/resources/extensions/gsd/auto.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +86 -0
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +51 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +51 -14
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +21 -10
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +4 -1
- package/src/resources/extensions/gsd/commands-verdict.ts +1 -1
- package/src/resources/extensions/gsd/config-overlay.ts +3 -1
- package/src/resources/extensions/gsd/error-classifier.ts +2 -1
- package/src/resources/extensions/gsd/exec-sandbox.ts +4 -0
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +10 -4
- package/src/resources/extensions/gsd/prompts/system.md +3 -1
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +3 -0
- package/src/resources/extensions/gsd/skill-activation.ts +20 -2
- package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +20 -0
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +6 -0
- package/src/resources/extensions/gsd/state-reconciliation/types.ts +1 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +16 -3
- package/src/resources/extensions/gsd/tests/commands-dispatcher-validation-block.test.ts +38 -3
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state-helpers.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +18 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +54 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +52 -0
- package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +84 -10
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +12 -2
- package/src/resources/extensions/gsd/tests/tui-header-lifecycle.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +29 -6
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +21 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +25 -0
- package/src/resources/extensions/gsd/tools/exec-tool.ts +130 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +440 -2
- package/src/resources/extensions/gsd/unit-context-manifest.ts +14 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +2 -0
- package/src/resources/extensions/gsd/workflow-mcp-auto-prep.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -1
- /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → eRWf-RI9bzbrwEurm_3uI}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{xACmObbrDjwLriepRgaa9 → eRWf-RI9bzbrwEurm_3uI}/_ssgManifest.js +0 -0
|
@@ -413,6 +413,92 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
413
413
|
pi.registerTool(summarySaveTool);
|
|
414
414
|
registerAlias(pi, summarySaveTool, "gsd_save_summary", "gsd_summary_save");
|
|
415
415
|
|
|
416
|
+
// ─── gsd_uat_result_save ─────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
const uatResultSaveExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
|
419
|
+
const { executeUatResultSave } = await loadWorkflowExecutors();
|
|
420
|
+
return executeUatResultSave(params, resolveWorkflowToolBasePath(_ctx, params));
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const uatEvidenceRef = Type.Object({
|
|
424
|
+
kind: StringEnum(["gsd_uat_exec", "gsd_exec", "screenshot", "log", "url", "browser"], { description: "Evidence kind" }),
|
|
425
|
+
ref: Type.String({ description: "Evidence ID, approved .gsd path, or URL" }),
|
|
426
|
+
note: Type.Optional(Type.String({ description: "Short evidence note" })),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const uatCheck = Type.Object({
|
|
430
|
+
id: Type.String({ description: "Stable check ID from the UAT spec" }),
|
|
431
|
+
description: Type.String({ description: "Check description" }),
|
|
432
|
+
mode: StringEnum(["artifact", "runtime", "browser", "human-follow-up"], { description: "Evidence mode" }),
|
|
433
|
+
result: StringEnum(["PASS", "FAIL", "NEEDS-HUMAN"], { description: "Check result" }),
|
|
434
|
+
evidence: Type.Optional(Type.Array(uatEvidenceRef, { description: "Objective evidence references" })),
|
|
435
|
+
notes: Type.Optional(Type.String({ description: "Observed result, failure notes, or human instruction" })),
|
|
436
|
+
nonAutomatable: Type.Optional(Type.Boolean({ description: "True when the check is explicitly non-automatable" })),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const toolPresentationBlock = Type.Object({
|
|
440
|
+
surface: StringEnum(["provider-tools", "claude-code-sdk", "mcp", "hybrid"], { description: "Tool presentation surface" }),
|
|
441
|
+
model: Type.Optional(Type.Object({
|
|
442
|
+
provider: Type.Optional(Type.String()),
|
|
443
|
+
api: Type.Optional(Type.String()),
|
|
444
|
+
id: Type.Optional(Type.String()),
|
|
445
|
+
})),
|
|
446
|
+
presentedTools: Type.Array(Type.String(), { description: "Tool names actually presented to the model" }),
|
|
447
|
+
blockedTools: Type.Array(Type.Object({
|
|
448
|
+
name: Type.String(),
|
|
449
|
+
reason: Type.String(),
|
|
450
|
+
}), { description: "Tool names blocked from the model with reasons" }),
|
|
451
|
+
aliases: Type.Optional(Type.Array(Type.Object({
|
|
452
|
+
requested: Type.String(),
|
|
453
|
+
canonical: Type.String(),
|
|
454
|
+
}))),
|
|
455
|
+
fallbackToolsUsed: Type.Optional(Type.Array(Type.String())),
|
|
456
|
+
toolPresentationPlanId: Type.Optional(Type.String()),
|
|
457
|
+
notes: Type.Optional(Type.String()),
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const uatResultSaveTool = {
|
|
461
|
+
name: "gsd_uat_result_save",
|
|
462
|
+
label: "Save UAT Result",
|
|
463
|
+
description:
|
|
464
|
+
"Save a structured UAT result for a slice. Validates evidence, writes the ASSESSMENT artifact, " +
|
|
465
|
+
"records attempt history, and saves the aggregate UAT gate result.",
|
|
466
|
+
promptSnippet: "Save structured UAT checks, evidence, verdict, and tool-presentation proof",
|
|
467
|
+
promptGuidelines: [
|
|
468
|
+
"Call gsd_uat_result_save once after all UAT checks have been executed.",
|
|
469
|
+
"Every PASS or FAIL check must cite objective evidence, preferably a gsd_uat_exec evidence ID.",
|
|
470
|
+
"Include the presented and blocked tool set in presentation so tool timing is auditable.",
|
|
471
|
+
"Do not use raw gsd_summary_save as a substitute for UAT results.",
|
|
472
|
+
],
|
|
473
|
+
parameters: Type.Object({
|
|
474
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
475
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
476
|
+
uatType: StringEnum(["artifact-driven", "browser-executable", "runtime-executable", "live-runtime", "mixed", "human-experience"], { description: "Declared UAT mode" }),
|
|
477
|
+
verdict: StringEnum(["PASS", "FAIL", "PARTIAL"], { description: "Overall UAT verdict" }),
|
|
478
|
+
checks: Type.Array(uatCheck, { description: "Structured check results" }),
|
|
479
|
+
presentation: toolPresentationBlock,
|
|
480
|
+
notes: Type.Optional(Type.String({ description: "Overall verdict rationale" })),
|
|
481
|
+
attempt: Type.Optional(Type.String({ description: "Attempt number or auto" })),
|
|
482
|
+
previousAttemptId: Type.Optional(Type.String({ description: "Prior attempt ID, when retrying" })),
|
|
483
|
+
}),
|
|
484
|
+
execute: uatResultSaveExecute,
|
|
485
|
+
renderCall(args: any, theme: any) {
|
|
486
|
+
let text = theme.fg("toolTitle", theme.bold("uat_result_save "));
|
|
487
|
+
text += theme.fg("accent", `${args.milestoneId ?? "?"}/${args.sliceId ?? "?"}`);
|
|
488
|
+
if (args.verdict) text += theme.fg("dim", ` → ${args.verdict}`);
|
|
489
|
+
return new Text(text, 0, 0);
|
|
490
|
+
},
|
|
491
|
+
renderResult(result: any, _options: any, theme: any) {
|
|
492
|
+
const d = readDetails(result);
|
|
493
|
+
if (result.isError || d?.error) {
|
|
494
|
+
return new Text(theme.fg("error", formatToolErrorText(result, d)), 0, 0);
|
|
495
|
+
}
|
|
496
|
+
return new Text(theme.fg("success", `UAT ${d?.sliceId ?? ""}: ${d?.verdict ?? "saved"}`), 0, 0);
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
pi.registerTool(uatResultSaveTool);
|
|
501
|
+
|
|
416
502
|
// ─── gsd_milestone_generate_id (formerly gsd_generate_milestone_id) ────
|
|
417
503
|
|
|
418
504
|
const milestoneGenerateIdExecute = async (_toolCallId: string, _params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
|
@@ -25,6 +25,57 @@ async function loadContextModePreferences(baseDir: string) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function registerExecTools(pi: ExtensionAPI): void {
|
|
28
|
+
pi.registerTool({
|
|
29
|
+
name: "gsd_uat_exec",
|
|
30
|
+
label: "UAT Exec",
|
|
31
|
+
description:
|
|
32
|
+
"Run a UAT-scoped bash/node/python check with milestone/slice/check metadata. " +
|
|
33
|
+
"Uses the same capped .gsd/exec evidence store as gsd_exec, but rejects commands that mutate dependencies, git state, credentials, or destructive files.",
|
|
34
|
+
promptSnippet: "Run one UAT check and save typed evidence under .gsd/exec",
|
|
35
|
+
promptGuidelines: [
|
|
36
|
+
"Use gsd_uat_exec for each automated UAT check.",
|
|
37
|
+
"Every PASS/FAIL check saved by gsd_uat_result_save must reference objective evidence from this tool or another approved GSD evidence path.",
|
|
38
|
+
"Do not install packages, mutate git state, edit source files, or dump credentials during UAT.",
|
|
39
|
+
],
|
|
40
|
+
parameters: Type.Object({
|
|
41
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
42
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S01)" }),
|
|
43
|
+
checkId: Type.String({ description: "Stable check ID from the UAT spec (e.g. UAT-01)" }),
|
|
44
|
+
intent: Type.String({
|
|
45
|
+
description:
|
|
46
|
+
"UAT command intent. Use one canonical value: uat-artifact-check, uat-runtime-check, " +
|
|
47
|
+
"uat-browser-check, uat-service-start, or uat-log-inspection. Short aliases such as artifact, " +
|
|
48
|
+
"runtime, browser, service-start, and log-inspection are accepted.",
|
|
49
|
+
}),
|
|
50
|
+
runtime: Type.Optional(
|
|
51
|
+
Type.String({
|
|
52
|
+
description:
|
|
53
|
+
"Optional interpreter. Defaults to bash. Supported: bash, node, python; sh/shell, js/nodejs, and py/python3 aliases are accepted.",
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
script: Type.Optional(Type.String({ description: "Script body. Keep output small (log the finding, not the data)." })),
|
|
57
|
+
command: Type.Optional(Type.String({ description: "Alias for script; defaults to bash when runtime is omitted." })),
|
|
58
|
+
cmd: Type.Optional(Type.String({ description: "Short alias for script." })),
|
|
59
|
+
code: Type.Optional(Type.String({ description: "Alias for script, useful for node/python snippets." })),
|
|
60
|
+
expected: Type.Optional(Type.String({ description: "Expected outcome for this UAT check." })),
|
|
61
|
+
timeout_ms: Type.Optional(
|
|
62
|
+
Type.Number({
|
|
63
|
+
description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
|
|
64
|
+
minimum: 1_000,
|
|
65
|
+
maximum: 600_000,
|
|
66
|
+
}),
|
|
67
|
+
),
|
|
68
|
+
}),
|
|
69
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
70
|
+
const { executeUatExec } = await import("../tools/exec-tool.js");
|
|
71
|
+
const baseDir = resolveCtxCwd(_ctx);
|
|
72
|
+
return executeUatExec(params as Parameters<typeof executeUatExec>[0], {
|
|
73
|
+
baseDir,
|
|
74
|
+
preferences: await loadContextModePreferences(baseDir),
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
28
79
|
pi.registerTool({
|
|
29
80
|
name: "gsd_exec",
|
|
30
81
|
label: "Exec (Sandboxed)",
|
|
@@ -36,7 +36,9 @@ import { resolveSkillManifest } from "../skill-manifest.js";
|
|
|
36
36
|
import { applyUnitSkillVisibility, unitHasSkillManifest } from "../skill-scope.js";
|
|
37
37
|
import { getGuidedUnitContext } from "../guided-unit-context.js";
|
|
38
38
|
import { registerPlanMilestoneSchemaRecovery } from "./plan-milestone-schema-recovery.js";
|
|
39
|
-
import { AUTO_UNIT_SCOPED_TOOLS, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
39
|
+
import { AUTO_UNIT_SCOPED_TOOLS, RUN_UAT_BROWSER_TOOL_NAMES, isWorkflowAliasTool } from "../auto-unit-tool-scope.js";
|
|
40
|
+
import { filterToolsForProvider } from "../model-router.js";
|
|
41
|
+
import { RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
40
42
|
|
|
41
43
|
let approvalQuestionAbortInFlight = false;
|
|
42
44
|
|
|
@@ -123,6 +125,7 @@ export const MINIMAL_GSD_TOOL_NAMES = [
|
|
|
123
125
|
"gsd_resume",
|
|
124
126
|
"gsd_milestone_status",
|
|
125
127
|
"gsd_checkpoint_db",
|
|
128
|
+
"gsd_plan_milestone",
|
|
126
129
|
"memory_query",
|
|
127
130
|
"capture_thought",
|
|
128
131
|
] as const;
|
|
@@ -226,6 +229,9 @@ export function buildMinimalAutoGsdToolSet(
|
|
|
226
229
|
unitType: string | undefined,
|
|
227
230
|
registeredToolNames: readonly string[] = activeToolNames,
|
|
228
231
|
): string[] {
|
|
232
|
+
if (unitType === "run-uat") {
|
|
233
|
+
return buildRunUatGsdToolSet(activeToolNames, registeredToolNames);
|
|
234
|
+
}
|
|
229
235
|
const unitTools = unitType ? AUTO_UNIT_SCOPED_TOOLS[unitType] ?? [] : [];
|
|
230
236
|
const autoBaseTools = new Set<string>(MINIMAL_AUTO_BASE_TOOL_NAMES);
|
|
231
237
|
const availableBaseTools = registeredToolNames.filter((name) => autoBaseTools.has(name));
|
|
@@ -240,6 +246,17 @@ export function buildMinimalAutoGsdToolSet(
|
|
|
240
246
|
return withPreservedShimTools([...new Set([...preserved, ...scoped])]);
|
|
241
247
|
}
|
|
242
248
|
|
|
249
|
+
export function buildRunUatGsdToolSet(
|
|
250
|
+
activeToolNames: readonly string[],
|
|
251
|
+
registeredToolNames: readonly string[] = activeToolNames,
|
|
252
|
+
): string[] {
|
|
253
|
+
const scoped = resolveScopedToolNames(
|
|
254
|
+
[...activeToolNames, ...registeredToolNames],
|
|
255
|
+
[...RUN_UAT_WORKFLOW_TOOL_NAMES, "subagent", ...RUN_UAT_BROWSER_TOOL_NAMES],
|
|
256
|
+
);
|
|
257
|
+
return [...new Set(scoped)];
|
|
258
|
+
}
|
|
259
|
+
|
|
243
260
|
export function buildMinimalGsdWorkflowToolSet(
|
|
244
261
|
activeToolNames: readonly string[],
|
|
245
262
|
registeredToolNames: readonly string[] = activeToolNames,
|
|
@@ -1022,9 +1039,8 @@ export function registerHooks(
|
|
|
1022
1039
|
if (result.block) return result;
|
|
1023
1040
|
});
|
|
1024
1041
|
|
|
1025
|
-
// ── Safety harness: evidence collection + destructive command
|
|
1042
|
+
// ── Safety harness: evidence collection + destructive command blocking ──
|
|
1026
1043
|
pi.on("tool_call", async (event, ctx) => {
|
|
1027
|
-
if (!isAutoActive()) return;
|
|
1028
1044
|
markToolStart(event.toolCallId, event.toolName);
|
|
1029
1045
|
safetyRecordToolCall(event.toolCallId, event.toolName, event.input as Record<string, unknown>);
|
|
1030
1046
|
|
|
@@ -1041,17 +1057,28 @@ export function registerHooks(
|
|
|
1041
1057
|
}
|
|
1042
1058
|
}
|
|
1043
1059
|
|
|
1044
|
-
// Destructive command classification
|
|
1060
|
+
// Destructive command classification + hard gate in all modes.
|
|
1045
1061
|
if (isToolCallEventType("bash", event)) {
|
|
1046
1062
|
const classification = classifyCommand(event.input.command);
|
|
1047
1063
|
if (classification.destructive) {
|
|
1064
|
+
const reason = [
|
|
1065
|
+
"HARD BLOCK: destructive Bash command requires explicit human confirmation.",
|
|
1066
|
+
`Detected: ${classification.labels.join(", ")}`,
|
|
1067
|
+
"Run this via ask_user_questions, wait for the user's response,",
|
|
1068
|
+
"then issue the command only when confirmed in the current turn.",
|
|
1069
|
+
].join(" ");
|
|
1048
1070
|
safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
|
|
1049
1071
|
command: String(event.input.command).slice(0, 200),
|
|
1050
1072
|
});
|
|
1051
|
-
ctx
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1073
|
+
if (ctx) {
|
|
1074
|
+
await maybePauseAutoForApprovalGate(
|
|
1075
|
+
ctx,
|
|
1076
|
+
pi,
|
|
1077
|
+
isAutoActive(),
|
|
1078
|
+
"Depth confirmation is waiting for your answer — pausing auto-mode.",
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return { block: true, reason };
|
|
1055
1082
|
}
|
|
1056
1083
|
}
|
|
1057
1084
|
});
|
|
@@ -1320,19 +1347,27 @@ export function registerHooks(
|
|
|
1320
1347
|
const fullToolsRequested = isFullGsdToolSurfaceRequested();
|
|
1321
1348
|
const dropAliases = !fullToolsRequested;
|
|
1322
1349
|
const dropBrowser = !fullToolsRequested && !isBrowserToolSurfaceRequested();
|
|
1323
|
-
const
|
|
1324
|
-
(name) => !(dropAliases && isWorkflowAliasTool(name))
|
|
1350
|
+
const aliasFilteredCompatible = compatible.filter(
|
|
1351
|
+
(name) => !(dropAliases && isWorkflowAliasTool(name)),
|
|
1352
|
+
);
|
|
1353
|
+
const providerCompatible = aliasFilteredCompatible.filter(
|
|
1354
|
+
(name) => !(dropBrowser && isBrowserTool(name)),
|
|
1325
1355
|
);
|
|
1326
1356
|
const surfaceReduced = providerCompatible.length !== compatible.length;
|
|
1327
1357
|
if (fullToolsRequested) {
|
|
1328
1358
|
return surfaceReduced ? { toolNames: providerCompatible } : undefined;
|
|
1329
1359
|
}
|
|
1330
1360
|
const registeredToolNames = resolveRegisteredToolNames(pi, event.activeToolNames);
|
|
1361
|
+
const compatibleRegisteredToolNames = filterToolsForProvider(
|
|
1362
|
+
registeredToolNames,
|
|
1363
|
+
event.selectedModelApi,
|
|
1364
|
+
event.selectedModelProvider,
|
|
1365
|
+
).compatible.filter((name) => !(dropAliases && isWorkflowAliasTool(name)));
|
|
1331
1366
|
const guidedUnit = getGuidedUnitContext();
|
|
1332
1367
|
const requestScoped = buildRequestScopedGsdToolSet(
|
|
1333
|
-
providerCompatible,
|
|
1368
|
+
guidedUnit?.unitType === "run-uat" ? aliasFilteredCompatible : providerCompatible,
|
|
1334
1369
|
event.requestCustomMessages,
|
|
1335
|
-
registeredToolNames,
|
|
1370
|
+
guidedUnit?.unitType === "run-uat" ? compatibleRegisteredToolNames : registeredToolNames,
|
|
1336
1371
|
guidedUnit?.unitType,
|
|
1337
1372
|
);
|
|
1338
1373
|
if (requestScoped) {
|
|
@@ -1342,9 +1377,11 @@ export function registerHooks(
|
|
|
1342
1377
|
if (dash.active && dash.currentUnit) {
|
|
1343
1378
|
return {
|
|
1344
1379
|
toolNames: buildMinimalAutoGsdToolSet(
|
|
1345
|
-
providerCompatible,
|
|
1380
|
+
dash.currentUnit.type === "run-uat" ? aliasFilteredCompatible : providerCompatible,
|
|
1346
1381
|
dash.currentUnit.type,
|
|
1347
|
-
|
|
1382
|
+
dash.currentUnit.type === "run-uat"
|
|
1383
|
+
? compatibleRegisteredToolNames
|
|
1384
|
+
: resolveRegisteredToolNames(pi, event.activeToolNames),
|
|
1348
1385
|
),
|
|
1349
1386
|
};
|
|
1350
1387
|
}
|
|
@@ -679,6 +679,7 @@ const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
|
|
|
679
679
|
* manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
|
|
680
680
|
*/
|
|
681
681
|
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
682
|
+
mnemo: { readOnlySpecialist: true },
|
|
682
683
|
scout: { readOnlySpecialist: true },
|
|
683
684
|
planner: { readOnlySpecialist: true },
|
|
684
685
|
reviewer: { readOnlySpecialist: true },
|
|
@@ -692,7 +693,7 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
|
|
|
692
693
|
.map(([agentId]) => agentId),
|
|
693
694
|
);
|
|
694
695
|
|
|
695
|
-
let
|
|
696
|
+
let warnedMissingControlledDispatchAgentClasses = false;
|
|
696
697
|
|
|
697
698
|
function isReadOnlySpecialist(agentId: string): boolean {
|
|
698
699
|
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
|
|
@@ -703,11 +704,20 @@ function allowedPlanningDispatchAgentsList(): string {
|
|
|
703
704
|
return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
|
|
704
705
|
}
|
|
705
706
|
|
|
706
|
-
function
|
|
707
|
-
|
|
708
|
-
|
|
707
|
+
function allowsControlledSubagentDispatch(
|
|
708
|
+
policy: ToolsPolicy,
|
|
709
|
+
): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
|
|
710
|
+
return (
|
|
711
|
+
(policy.mode === "planning-dispatch" || policy.mode === "verification") &&
|
|
712
|
+
Array.isArray((policy as { readonly allowedSubagents?: unknown }).allowedSubagents)
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function warnMissingControlledDispatchAgentClasses(unitType: string, mode: string, toolName: string): void {
|
|
717
|
+
if (warnedMissingControlledDispatchAgentClasses) return;
|
|
718
|
+
warnedMissingControlledDispatchAgentClasses = true;
|
|
709
719
|
// TODO(#5060): Remove this migration shim once all subagent/task callers are verified to forward agent identities.
|
|
710
|
-
const message = `[write-gate]
|
|
720
|
+
const message = `[write-gate] controlled-dispatch: shouldBlockPlanningUnit called for tool "${toolName}" ` +
|
|
711
721
|
`on unit "${unitType}" without agentClasses - stale caller; blocking dispatch.`;
|
|
712
722
|
console.warn(message);
|
|
713
723
|
logWarning("intercept", message, {
|
|
@@ -777,8 +787,9 @@ function blockReason(unitType: string, mode: string, what: string): string {
|
|
|
777
787
|
* - "docs" → like "planning" but also allows writes to paths
|
|
778
788
|
* matching `allowedPathGlobs` relative to basePath.
|
|
779
789
|
* - "verification"
|
|
780
|
-
* → allows Bash for project verification commands,
|
|
781
|
-
* writes restricted to .gsd
|
|
790
|
+
* → allows Bash for project verification commands, keeps
|
|
791
|
+
* writes restricted to .gsd/, and permits subagent dispatch
|
|
792
|
+
* only when the manifest declares allowedSubagents.
|
|
782
793
|
*
|
|
783
794
|
* `pathOrCommand` is the file path for write/edit-shaped tools and the
|
|
784
795
|
* shell command for bash. Other tools ignore this argument.
|
|
@@ -825,7 +836,7 @@ export function shouldBlockPlanningUnit(
|
|
|
825
836
|
if (tool.startsWith("gsd_")) return { block: false };
|
|
826
837
|
|
|
827
838
|
if (PLANNING_SUBAGENT_TOOLS.has(tool)) {
|
|
828
|
-
if (policy
|
|
839
|
+
if (allowsControlledSubagentDispatch(policy)) {
|
|
829
840
|
const requested = (agentClasses ?? []).map(a => a.trim()).filter(Boolean);
|
|
830
841
|
const dispatchContract = compileSubagentPermissionContract(policy);
|
|
831
842
|
const allowedSubagents = dispatchContract.allowedSubagents;
|
|
@@ -834,7 +845,7 @@ export function shouldBlockPlanningUnit(
|
|
|
834
845
|
// agent identities yet. Block and warn so stale callers surface in telemetry
|
|
835
846
|
// instead of silently bypassing the gate.
|
|
836
847
|
if (agentClasses === undefined) {
|
|
837
|
-
|
|
848
|
+
warnMissingControlledDispatchAgentClasses(unitType, policy.mode, tool);
|
|
838
849
|
return {
|
|
839
850
|
block: true,
|
|
840
851
|
reason: blockReason(
|
|
@@ -857,7 +868,7 @@ export function shouldBlockPlanningUnit(
|
|
|
857
868
|
reason: blockReason(
|
|
858
869
|
unitType,
|
|
859
870
|
policy.mode,
|
|
860
|
-
`subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from
|
|
871
|
+
`subagent dispatch of "${globallyDisallowed}" not permitted; only read-only specialists (${allowedPlanningDispatchAgentsList()}) may be dispatched from ${policy.mode} units`,
|
|
861
872
|
),
|
|
862
873
|
};
|
|
863
874
|
}
|
|
@@ -75,7 +75,7 @@ export function showHelp(ctx: ExtensionCommandContext, args = ""): void {
|
|
|
75
75
|
" /gsd new-milestone Create milestone from headless context (used by gsd headless)",
|
|
76
76
|
" /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
|
|
77
77
|
" /gsd quick Execute a quick task without full planning overhead",
|
|
78
|
-
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
|
|
78
|
+
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|validate|reassess|uat|replan]",
|
|
79
79
|
" /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
|
|
80
80
|
" /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
|
|
81
81
|
" /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
|
|
@@ -23,6 +23,9 @@ import {
|
|
|
23
23
|
import { loadFile, saveFile, splitFrontmatter, parseFrontmatterMap } from "./files.js";
|
|
24
24
|
import { runClaudeImportFlow } from "./claude-import.js";
|
|
25
25
|
|
|
26
|
+
const DEFAULT_WIDGET_MODE = "small";
|
|
27
|
+
const WIDGET_MODE_OPTIONS = [DEFAULT_WIDGET_MODE, "full", "min", "off"] as const;
|
|
28
|
+
|
|
26
29
|
/** Extract body content after frontmatter closing delimiter, or null if none. */
|
|
27
30
|
function extractBodyAfterFrontmatter(content: string): string | null {
|
|
28
31
|
const closingIdx = content.indexOf("\n---", content.indexOf("---"));
|
|
@@ -1558,7 +1561,7 @@ async function configureAdvanced(ctx: ExtensionCommandContext, prefs: Record<str
|
|
|
1558
1561
|
prefs.min_request_interval_ms = minRequestInterval;
|
|
1559
1562
|
}
|
|
1560
1563
|
|
|
1561
|
-
const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode,
|
|
1564
|
+
const widget = await promptEnum(ctx, "Auto-mode widget display", prefs.widget_mode, WIDGET_MODE_OPTIONS, DEFAULT_WIDGET_MODE);
|
|
1562
1565
|
if (widget !== undefined) prefs.widget_mode = widget;
|
|
1563
1566
|
|
|
1564
1567
|
const experimental = (prefs.experimental as Record<string, unknown> | undefined) ?? {};
|
|
@@ -238,7 +238,7 @@ export async function handleVerdict(
|
|
|
238
238
|
|
|
239
239
|
if (effectiveVerdict === "needs-remediation") {
|
|
240
240
|
ctx.ui.notify(
|
|
241
|
-
"Follow up with
|
|
241
|
+
"Follow up with /gsd dispatch reassess to add remediation slices, then re-run /gsd auto.",
|
|
242
242
|
"info",
|
|
243
243
|
);
|
|
244
244
|
}
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
resolveAutoSupervisorConfig,
|
|
24
24
|
} from "./preferences.js";
|
|
25
25
|
|
|
26
|
+
const DEFAULT_WIDGET_MODE = "small";
|
|
27
|
+
|
|
26
28
|
// ─── Data Collection ──────────────────────────────────────────────────────
|
|
27
29
|
|
|
28
30
|
interface ConfigSection {
|
|
@@ -160,7 +162,7 @@ function collectConfigSections(): ConfigSection[] {
|
|
|
160
162
|
if (prefs?.service_tier) toggleRows.push({ label: "service_tier", value: prefs.service_tier });
|
|
161
163
|
if (prefs?.search_provider && prefs.search_provider !== "auto") toggleRows.push({ label: "search_provider", value: prefs.search_provider });
|
|
162
164
|
if (prefs?.context_selection) toggleRows.push({ label: "context_selection", value: prefs.context_selection });
|
|
163
|
-
if (prefs?.widget_mode && prefs.widget_mode !==
|
|
165
|
+
if (prefs?.widget_mode && prefs.widget_mode !== DEFAULT_WIDGET_MODE) toggleRows.push({ label: "widget_mode", value: prefs.widget_mode });
|
|
164
166
|
if (prefs?.experimental?.rtk) toggleRows.push({ label: "experimental.rtk", value: "on" });
|
|
165
167
|
if (toggleRows.length > 0) sections.push({ title: "Toggles", rows: toggleRows });
|
|
166
168
|
|
|
@@ -47,9 +47,10 @@ export function resetRetryState(state: RetryState): void {
|
|
|
47
47
|
const PERMANENT_RE = /auth|unauthorized|forbidden|invalid.*key|invalid.*api|billing|quota exceeded|account/i;
|
|
48
48
|
// Include provider-specific quota-window phrasing like:
|
|
49
49
|
// - "You've hit your limit"
|
|
50
|
+
// - "You've reached your limit"
|
|
50
51
|
// - "usage limit" / "quota reached"
|
|
51
52
|
// - "out of extra usage"
|
|
52
|
-
const RATE_LIMIT_RE = /rate.?limit|too many requests|429|hit your limit|usage limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
|
|
53
|
+
const RATE_LIMIT_RE = /rate.?limit|too many requests|429|(?:hit|reached) your (?:\w+ )?limit|(?:usage|session|weekly|daily|monthly|quota) limit|out of extra usage|quota (?:reached|hit)|limit.*resets?/i;
|
|
53
54
|
// OpenRouter affordability-style quota errors should be treated as transient
|
|
54
55
|
// so core retry logic can lower maxTokens and continue in-session.
|
|
55
56
|
const AFFORDABILITY_RE = /requires more credits|can only afford|insufficient credits|not enough credits|fewer max_tokens/i;
|
|
@@ -20,6 +20,8 @@ export interface ExecSandboxRequest {
|
|
|
20
20
|
script: string;
|
|
21
21
|
/** Optional purpose/label recorded in meta.json. */
|
|
22
22
|
purpose?: string;
|
|
23
|
+
/** Optional structured metadata recorded in meta.json. */
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
23
25
|
/** Per-invocation timeout in ms. Clamped to `clamp_timeout_ms`. */
|
|
24
26
|
timeout_ms?: number;
|
|
25
27
|
}
|
|
@@ -315,6 +317,7 @@ function writeMeta(
|
|
|
315
317
|
id: result.id,
|
|
316
318
|
runtime: result.runtime,
|
|
317
319
|
purpose: request.purpose ?? null,
|
|
320
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
318
321
|
script_chars: request.script.length,
|
|
319
322
|
started_at: now.toISOString(),
|
|
320
323
|
finished_at: new Date(now.getTime() + result.duration_ms).toISOString(),
|
|
@@ -328,6 +331,7 @@ function writeMeta(
|
|
|
328
331
|
stderr_truncated: result.stderr_truncated,
|
|
329
332
|
stdout_path: result.stdout_path,
|
|
330
333
|
stderr_path: result.stderr_path,
|
|
334
|
+
...(request.metadata ? { metadata: request.metadata } : {}),
|
|
331
335
|
};
|
|
332
336
|
writeFileSync(path, `${JSON.stringify(meta, null, 2)}\n`);
|
|
333
337
|
}
|
|
@@ -423,7 +423,7 @@ export interface GSDPreferences {
|
|
|
423
423
|
search_provider?: "brave" | "tavily" | "ollama" | "native" | "auto";
|
|
424
424
|
/** Context selection mode for file inlining. "full" inlines entire files, "smart" uses semantic chunking. Default derived from token profile. */
|
|
425
425
|
context_selection?: ContextSelectionMode;
|
|
426
|
-
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "
|
|
426
|
+
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "small". */
|
|
427
427
|
widget_mode?: "full" | "small" | "min" | "off";
|
|
428
428
|
/** Reactive (graph-derived parallel) task execution within slices. Disabled by default. */
|
|
429
429
|
reactive_execution?: ReactiveExecutionConfig;
|
|
@@ -37,7 +37,13 @@ You are the UAT runner. Execute every check defined in `{{uatPath}}` as deeply a
|
|
|
37
37
|
|
|
38
38
|
Choose the lightest tool that proves the check honestly:
|
|
39
39
|
|
|
40
|
-
- Run
|
|
40
|
+
- Run automated checks with `gsd_uat_exec`
|
|
41
|
+
- Use `uat-artifact-check` as `intent` for static file, grep, structure, or artifact checks.
|
|
42
|
+
- Use `uat-runtime-check` as `intent` for executing tests, scripts, or runtime assertions.
|
|
43
|
+
- Use `uat-browser-check` as `intent` for browser interaction or screenshot-backed UI checks.
|
|
44
|
+
- Use `uat-service-start` as `intent` only when starting or connecting to an app/service.
|
|
45
|
+
- Use `uat-log-inspection` as `intent` for checking logs or captured output files.
|
|
46
|
+
- The result-table evidence mode is separate; do not use `artifact`, `runtime`, or `human-follow-up` as `intent`.
|
|
41
47
|
- Run `grep` / `rg` checks against files
|
|
42
48
|
- Run `node` / other script invocations
|
|
43
49
|
- Read files and verify their contents
|
|
@@ -48,7 +54,7 @@ Choose the lightest tool that proves the check honestly:
|
|
|
48
54
|
For each check, record:
|
|
49
55
|
- The check description (from the UAT file)
|
|
50
56
|
- The evidence mode used: `artifact`, `runtime`, or `human-follow-up`
|
|
51
|
-
- The command or action taken
|
|
57
|
+
- The command or action taken, including the `gsd_uat_exec` evidence ID for automated checks
|
|
52
58
|
- The actual result observed
|
|
53
59
|
- `PASS`, `FAIL`, or `NEEDS-HUMAN`
|
|
54
60
|
|
|
@@ -57,7 +63,7 @@ After running all checks, compute the **overall verdict**:
|
|
|
57
63
|
- `FAIL` — one or more automatable checks failed
|
|
58
64
|
- `PARTIAL` — one or more automatable checks were skipped or returned inconclusive results (not the same as `NEEDS-HUMAN` — use PARTIAL only when the agent itself could not determine pass/fail for a check it was supposed to automate)
|
|
59
65
|
|
|
60
|
-
Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `slice_id: {{sliceId}}`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content
|
|
66
|
+
Call `gsd_summary_save` with `milestone_id: "{{milestoneId}}"`, `slice_id: "{{sliceId}}"`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content`. The tool computes the assessment path, persists to DB/disk, and saves the aggregate UAT gate. The content should follow this logical shape:
|
|
61
67
|
|
|
62
68
|
```markdown
|
|
63
69
|
---
|
|
@@ -86,6 +92,6 @@ date: <ISO 8601 timestamp>
|
|
|
86
92
|
|
|
87
93
|
---
|
|
88
94
|
|
|
89
|
-
**You MUST call `gsd_summary_save` with the UAT result content before finishing.**
|
|
95
|
+
**You MUST call `gsd_summary_save` with `artifact_type: "ASSESSMENT"` and the UAT result content before finishing. Do not write the assessment file directly.**
|
|
90
96
|
|
|
91
97
|
When done, say: "UAT {{sliceId}} complete."
|
|
@@ -32,7 +32,7 @@ GSD ships with bundled skills. Installed skills are listed in `<available_skills
|
|
|
32
32
|
- Never print, echo, log, or restate secrets or credentials. Report only key names and applied/skipped status.
|
|
33
33
|
- Never ask the user to edit `.env` files or set secrets manually. Use `secure_env_collect`.
|
|
34
34
|
- In enduring files, write current state only unless the file is explicitly historical.
|
|
35
|
-
- **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
|
|
35
|
+
- **Never take outward-facing actions on GitHub or external services without explicit user confirmation.** This includes creating/closing issues, merging/approving/commenting on PRs, pushing remote branches, publishing packages, terragrunt/aws/kubectl mutations, or any state change outside local filesystem. Read-only listing/viewing/diffing is fine. Present intent and get a clear "yes" first. **Non-bypassable:** no response, ambiguity, or `ask_user_questions` failure means re-ask; never rationalize past the block. Missing "yes" means "no."
|
|
36
36
|
|
|
37
37
|
If a `GSD Skill Preferences` block appears below, treat it as durable guidance for skills to use, prefer, or avoid unless it conflicts with artifact rules, verification, or higher-priority instructions.
|
|
38
38
|
|
|
@@ -160,4 +160,6 @@ Fix root causes, not symptoms. If applying temporary mitigation, label it and pr
|
|
|
160
160
|
- When debugging, stay curious. Problems are puzzles. Say what's interesting about the failure before reaching for fixes.
|
|
161
161
|
- After completing a task, give a brief summary and 2-4 numbered next-step options; last option is always "Other". Omit the list for strict output formats.
|
|
162
162
|
|
|
163
|
+
If any next step is destructive/outward-facing, present it via `ask_user_questions` and wait for the user's answer before execution. Do not execute a next-step item from a prior plain-text numbered list without fresh confirmation.
|
|
164
|
+
|
|
163
165
|
Good narration states a decision or finding: "Three handlers follow a middleware pattern - using that instead of a custom wrapper." Bad narration just announces the next call ("Reading the file now.") or emits compressed planner notes ("Need create plan artifact maybe read existing plans.").
|
|
@@ -24,6 +24,9 @@ const DESTRUCTIVE_PATTERNS: readonly DestructivePattern[] = [
|
|
|
24
24
|
{ pattern: /\btruncate\s+table\b/i, label: "SQL truncate" },
|
|
25
25
|
{ pattern: /\bchmod\s+777\b/, label: "world-writable permissions" },
|
|
26
26
|
{ pattern: /\bcurl\s.*\|\s*(bash|sh|zsh)\b/, label: "pipe to shell" },
|
|
27
|
+
{ pattern: /\bterra(form|grunt)\s+(apply|destroy)/i, label: "IaC apply/destroy" },
|
|
28
|
+
{ pattern: /\baws\s+\w+\s+(delete|create|put|remove|terminate)\b/i, label: "AWS mutation" },
|
|
29
|
+
{ pattern: /\bkubectl\s+(delete|apply)\b/i, label: "kubectl mutation" },
|
|
27
30
|
];
|
|
28
31
|
|
|
29
32
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -50,6 +50,16 @@ function tokenizeSkillContext(...parts: Array<string | null | undefined>): Set<s
|
|
|
50
50
|
return tokens;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function tokenizeUnitType(unitType: string | undefined): Set<string> {
|
|
54
|
+
const tokens = new Set<string>();
|
|
55
|
+
const value = unitType?.trim().toLowerCase();
|
|
56
|
+
if (!value) return tokens;
|
|
57
|
+
tokens.add(value);
|
|
58
|
+
tokens.add(value.replace(/[-_]+/g, " "));
|
|
59
|
+
tokens.add(value.replace(/[-_\s]+/g, ""));
|
|
60
|
+
return tokens;
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
function skillMatchesContext(skill: Skill, contextTokens: Set<string>): boolean {
|
|
54
64
|
const haystacks = [
|
|
55
65
|
skill.name.toLowerCase(),
|
|
@@ -79,17 +89,25 @@ function ruleMatchesContext(when: string, contextTokens: Set<string>): boolean {
|
|
|
79
89
|
);
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
function ruleMatchesUnitType(when: string, unitType: string | undefined): boolean {
|
|
93
|
+
if (!unitType) return false;
|
|
94
|
+
const whenTokens = tokenizeSkillContext(when);
|
|
95
|
+
const unitTokens = tokenizeUnitType(unitType);
|
|
96
|
+
return [...unitTokens].some(token => whenTokens.has(token));
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
function resolveSkillRuleMatches(
|
|
83
100
|
prefs: GSDPreferences | undefined,
|
|
84
101
|
contextTokens: Set<string>,
|
|
85
102
|
base: string,
|
|
103
|
+
unitType?: string,
|
|
86
104
|
): { include: string[]; avoid: string[] } {
|
|
87
105
|
if (!prefs?.skill_rules?.length) return { include: [], avoid: [] };
|
|
88
106
|
|
|
89
107
|
const include: string[] = [];
|
|
90
108
|
const avoid: string[] = [];
|
|
91
109
|
for (const rule of prefs.skill_rules) {
|
|
92
|
-
if (!ruleMatchesContext(rule.when, contextTokens)) continue;
|
|
110
|
+
if (!ruleMatchesContext(rule.when, contextTokens) && !ruleMatchesUnitType(rule.when, unitType)) continue;
|
|
93
111
|
include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
|
|
94
112
|
avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
|
|
95
113
|
}
|
|
@@ -196,7 +214,7 @@ export function buildSkillActivationBlock(params: {
|
|
|
196
214
|
matched.add(name);
|
|
197
215
|
}
|
|
198
216
|
|
|
199
|
-
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
|
|
217
|
+
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base, params.unitType);
|
|
200
218
|
for (const name of ruleMatches.include) matched.add(name);
|
|
201
219
|
for (const name of ruleMatches.avoid) avoided.add(name);
|
|
202
220
|
|