@opengsd/gsd-pi 1.8.1-dev.897b3c90 → 1.9.0-dev.5c1a3ff2
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 +10 -8
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -17
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
- package/dist/resources/extensions/gsd/constants.js +12 -13
- package/dist/resources/extensions/gsd/context-masker.js +97 -21
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
- package/dist/resources/extensions/gsd/git-service.js +19 -7
- package/dist/resources/extensions/gsd/guided-flow.js +9 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +2 -12
- package/dist/resources/extensions/gsd/md-importer.js +5 -6
- package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +11 -0
- package/dist/resources/extensions/gsd/paths.js +51 -10
- package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
- package/dist/resources/extensions/gsd/preferences-types.js +5 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
- package/dist/resources/extensions/gsd/preferences.js +17 -0
- package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
- package/dist/resources/extensions/gsd/skill-activation.js +17 -3
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
- package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
- package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
- package/dist/resources/extensions/gsd/workspace.js +2 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- 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 +5 -5
- 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/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +1 -0
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/package.json +1 -1
- package/packages/mcp-server/README.md +7 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +5 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +59 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +53 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +6 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -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/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +104 -15
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
- package/src/resources/extensions/gsd/constants.ts +14 -13
- package/src/resources/extensions/gsd/context-masker.ts +98 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
- package/src/resources/extensions/gsd/git-service.ts +19 -6
- package/src/resources/extensions/gsd/guided-flow.ts +9 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +2 -14
- package/src/resources/extensions/gsd/md-importer.ts +5 -5
- package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
- package/src/resources/extensions/gsd/model-router.ts +12 -0
- package/src/resources/extensions/gsd/paths.ts +58 -10
- package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
- package/src/resources/extensions/gsd/preferences.ts +25 -0
- package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
- package/src/resources/extensions/gsd/skill-activation.ts +17 -3
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
- package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
- package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
- package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
- package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
- package/src/resources/extensions/gsd/workspace.ts +2 -1
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
|
@@ -17,6 +17,7 @@ import { getGateIdsForTurn } from "./gate-registry.js";
|
|
|
17
17
|
import {
|
|
18
18
|
KNOWN_PREFERENCE_KEYS,
|
|
19
19
|
KNOWN_UNIT_LABELS,
|
|
20
|
+
CONFIGURABLE_PLANNING_SUBAGENT_UNITS,
|
|
20
21
|
GSD_MODEL_PHASE_KEYS,
|
|
21
22
|
|
|
22
23
|
SKILL_ACTIONS,
|
|
@@ -26,6 +27,10 @@ import {
|
|
|
26
27
|
type GSDSkillRule,
|
|
27
28
|
type GSDThinkingLevel,
|
|
28
29
|
} from "./preferences-types.js";
|
|
30
|
+
import {
|
|
31
|
+
ALLOWED_PLANNING_DISPATCH_AGENTS,
|
|
32
|
+
allowedPlanningDispatchAgentsList,
|
|
33
|
+
} from "./planning-subagent-registry.js";
|
|
29
34
|
|
|
30
35
|
const VALID_TOKEN_PROFILES = new Set<TokenProfile>(["budget", "balanced", "quality", "burn-max"]);
|
|
31
36
|
const VALID_THINKING_LEVELS = new Set<GSDThinkingLevel>([
|
|
@@ -747,6 +752,63 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
747
752
|
}
|
|
748
753
|
}
|
|
749
754
|
|
|
755
|
+
// ─── Planning Subagents ─────────────────────────────────────────────────
|
|
756
|
+
if (preferences.planning_subagents !== undefined) {
|
|
757
|
+
if (
|
|
758
|
+
!preferences.planning_subagents ||
|
|
759
|
+
typeof preferences.planning_subagents !== "object" ||
|
|
760
|
+
Array.isArray(preferences.planning_subagents)
|
|
761
|
+
) {
|
|
762
|
+
errors.push("planning_subagents must be an object keyed by planning unit type");
|
|
763
|
+
} else {
|
|
764
|
+
const validUnits = new Set<string>(CONFIGURABLE_PLANNING_SUBAGENT_UNITS);
|
|
765
|
+
const validPlanningSubagents: NonNullable<GSDPreferences["planning_subagents"]> = {};
|
|
766
|
+
|
|
767
|
+
for (const [unitType, rawConfig] of Object.entries(preferences.planning_subagents as Record<string, unknown>)) {
|
|
768
|
+
if (!validUnits.has(unitType)) {
|
|
769
|
+
errors.push(
|
|
770
|
+
`planning_subagents unknown or unsupported unit "${unitType}" — supported units: ` +
|
|
771
|
+
`${CONFIGURABLE_PLANNING_SUBAGENT_UNITS.join(", ")}`,
|
|
772
|
+
);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
776
|
+
errors.push(`planning_subagents.${unitType} must be an object with an allowed array`);
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const allowed = normalizeStringArray((rawConfig as Record<string, unknown>).allowed);
|
|
781
|
+
if (allowed.length === 0) {
|
|
782
|
+
errors.push(`planning_subagents.${unitType}.allowed must include at least one read-only planning specialist`);
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const sanitizedAllowed: string[] = [];
|
|
787
|
+
for (const agentId of allowed) {
|
|
788
|
+
if (!ALLOWED_PLANNING_DISPATCH_AGENTS.has(agentId)) {
|
|
789
|
+
errors.push(
|
|
790
|
+
`planning_subagents.${unitType}.allowed includes "${agentId}", which is not classified as a ` +
|
|
791
|
+
`read-only planning specialist. Allowed agents: ${allowedPlanningDispatchAgentsList()}`,
|
|
792
|
+
);
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
sanitizedAllowed.push(agentId);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const dedupedAllowed = Array.from(new Set(sanitizedAllowed));
|
|
799
|
+
if (dedupedAllowed.length > 0) {
|
|
800
|
+
validPlanningSubagents[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>] = {
|
|
801
|
+
allowed: dedupedAllowed,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (Object.keys(validPlanningSubagents).length > 0) {
|
|
807
|
+
validated.planning_subagents = validPlanningSubagents;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
750
812
|
// ─── Dynamic Routing ─────────────────────────────────────────────────
|
|
751
813
|
if (preferences.dynamic_routing !== undefined) {
|
|
752
814
|
if (typeof preferences.dynamic_routing === "object" && preferences.dynamic_routing !== null) {
|
|
@@ -817,6 +817,7 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
817
817
|
: undefined,
|
|
818
818
|
post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
|
|
819
819
|
pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
|
|
820
|
+
planning_subagents: mergePlanningSubagents(base.planning_subagents, override.planning_subagents),
|
|
820
821
|
dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
|
|
821
822
|
? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
|
|
822
823
|
: undefined,
|
|
@@ -943,6 +944,30 @@ function mergePreDispatchHooks(
|
|
|
943
944
|
return merged.length > 0 ? merged : undefined;
|
|
944
945
|
}
|
|
945
946
|
|
|
947
|
+
function mergePlanningSubagents(
|
|
948
|
+
base?: GSDPreferences["planning_subagents"],
|
|
949
|
+
override?: GSDPreferences["planning_subagents"],
|
|
950
|
+
): GSDPreferences["planning_subagents"] | undefined {
|
|
951
|
+
if (!base && !override) return undefined;
|
|
952
|
+
const merged: NonNullable<GSDPreferences["planning_subagents"]> = {};
|
|
953
|
+
const unitTypes = new Set([
|
|
954
|
+
...Object.keys(base ?? {}),
|
|
955
|
+
...Object.keys(override ?? {}),
|
|
956
|
+
]);
|
|
957
|
+
|
|
958
|
+
for (const unitType of unitTypes) {
|
|
959
|
+
const allowed = mergeStringLists(
|
|
960
|
+
base?.[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>]?.allowed,
|
|
961
|
+
override?.[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>]?.allowed,
|
|
962
|
+
);
|
|
963
|
+
if (allowed?.length) {
|
|
964
|
+
merged[unitType as keyof NonNullable<GSDPreferences["planning_subagents"]>] = { allowed };
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
969
|
+
}
|
|
970
|
+
|
|
946
971
|
// ─── System Prompt Rendering ──────────────────────────────────────────────────
|
|
947
972
|
|
|
948
973
|
export function renderPreferencesForSystemPrompt(
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* Provider Payload Policy - ordered shaping of provider request payloads.
|
|
3
3
|
*
|
|
4
4
|
* The order is intentional:
|
|
5
|
-
* 1.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* 1. superseded GSD context injections (memory/guided/forensics) are removed,
|
|
6
|
+
* keeping only the latest, for every mode,
|
|
7
|
+
* 2. observation budgeting masks old tool results in auto-mode,
|
|
8
|
+
* 3. display truncation caps tool-result text for every mode,
|
|
9
|
+
* 4. the protected Source Context Block is appended after truncation,
|
|
10
|
+
* 5. supported models receive the configured service tier.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import type { ContextManagementConfig } from "./preferences-types.js";
|
|
@@ -14,6 +16,8 @@ import type { ServiceTierSetting } from "./service-tier.js";
|
|
|
14
16
|
import {
|
|
15
17
|
createObservationMask,
|
|
16
18
|
createResponsesInputObservationMask,
|
|
19
|
+
filterSupersededContextInjections,
|
|
20
|
+
filterSupersededResponsesContextInjections,
|
|
17
21
|
truncateContextResultMessages,
|
|
18
22
|
truncateResponsesInputResultItems,
|
|
19
23
|
} from "./context-masker.js";
|
|
@@ -80,10 +84,20 @@ function applyContextManagement(
|
|
|
80
84
|
deps: ProviderPayloadPolicyDeps,
|
|
81
85
|
): void {
|
|
82
86
|
const config = deps.loadContextManagementConfig();
|
|
87
|
+
applyContextInjectionFilter(payload);
|
|
83
88
|
applyObservationBudget(payload, config, deps.isAutoActive());
|
|
84
89
|
applyDisplayTruncation(payload, config);
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
function applyContextInjectionFilter(payload: Record<string, unknown>): void {
|
|
93
|
+
if (Array.isArray(payload.messages)) {
|
|
94
|
+
payload.messages = filterSupersededContextInjections(payload.messages as MessagePayload);
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(payload.input)) {
|
|
97
|
+
payload.input = filterSupersededResponsesContextInjections(payload.input as ResponsesInputPayload);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
function applyObservationBudget(
|
|
88
102
|
payload: Record<string, unknown>,
|
|
89
103
|
config: ContextManagementConfig | undefined,
|
|
@@ -14,7 +14,7 @@ interface DestructivePattern {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const DESTRUCTIVE_PATTERNS: readonly DestructivePattern[] = [
|
|
17
|
-
{ pattern: /\brm\s+(
|
|
17
|
+
{ pattern: /\brm\s+((?:-(?!-)[^\s]*[rR][^\s]*|--recursive)\s+|.*\s+(?:-(?!-)[^\s]*[rR][^\s]*|--recursive)(?:\s|$))/, label: "recursive delete" },
|
|
18
18
|
{ pattern: /\bgit\s+push\s+.*--force/, label: "force push" },
|
|
19
19
|
{ pattern: /\bgit\s+push\s+-f\b/, label: "force push" },
|
|
20
20
|
{ pattern: /\bgit\s+reset\s+--hard/, label: "hard reset" },
|
|
@@ -131,10 +131,23 @@ function resolvePreferredSkillNames(
|
|
|
131
131
|
* to prevent prompt injection via crafted directory names. */
|
|
132
132
|
const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
133
133
|
|
|
134
|
-
function
|
|
134
|
+
function escapeXml(str: string): string {
|
|
135
|
+
return str
|
|
136
|
+
.replace(/&/g, "&")
|
|
137
|
+
.replace(/</g, "<")
|
|
138
|
+
.replace(/>/g, ">")
|
|
139
|
+
.replace(/"/g, """)
|
|
140
|
+
.replace(/'/g, "'");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatSkillActivationBlock(skillNames: string[], skillPaths: Map<string, string>): string {
|
|
135
144
|
const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
|
|
136
145
|
if (safe.length === 0) return "";
|
|
137
|
-
const reads = safe.map(name =>
|
|
146
|
+
const reads = safe.map(name => {
|
|
147
|
+
const path = skillPaths.get(name);
|
|
148
|
+
if (path) return `Read the skill file at \`${escapeXml(path)}\``;
|
|
149
|
+
return `Find '${name}' in <available_skills>, copy its <location> value, and pass that exact path to read`;
|
|
150
|
+
}).join(". ");
|
|
138
151
|
return `<skill_activation>${reads}.</skill_activation>`;
|
|
139
152
|
}
|
|
140
153
|
|
|
@@ -203,6 +216,7 @@ export function buildSkillActivationBlock(params: {
|
|
|
203
216
|
const visibleSkills = loaded;
|
|
204
217
|
const manifestScopedSkills = filterSkillsByManifest(visibleSkills, params.unitType);
|
|
205
218
|
const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
|
|
219
|
+
const installedSkillPaths = new Map(visibleSkills.map(skill => [normalizeSkillReference(skill.name), skill.filePath]));
|
|
206
220
|
warnIfManifestHasMissingSkills(params.unitType, installedNames);
|
|
207
221
|
const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
|
|
208
222
|
const matched = new Set<string>();
|
|
@@ -252,7 +266,7 @@ export function buildSkillActivationBlock(params: {
|
|
|
252
266
|
const ordered = [...matched]
|
|
253
267
|
.filter(name => installedNames.has(name) && !avoided.has(name))
|
|
254
268
|
.sort();
|
|
255
|
-
const activationBlock = formatSkillActivationBlock(ordered);
|
|
269
|
+
const activationBlock = formatSkillActivationBlock(ordered, installedSkillPaths);
|
|
256
270
|
|
|
257
271
|
// Omit recommendations when the system catalog is manifest-scoped for this
|
|
258
272
|
// unit — skill names are already listed in <available_skills>.
|
|
@@ -92,6 +92,11 @@ remote_questions:
|
|
|
92
92
|
uat_dispatch:
|
|
93
93
|
post_unit_hooks: []
|
|
94
94
|
pre_dispatch_hooks: []
|
|
95
|
+
# planning_subagents:
|
|
96
|
+
# plan-milestone:
|
|
97
|
+
# allowed: [scout, planner, security]
|
|
98
|
+
# plan-slice:
|
|
99
|
+
# allowed: [scout, planner, reviewer, security]
|
|
95
100
|
# language:
|
|
96
101
|
# experimental:
|
|
97
102
|
# rtk: false
|
|
@@ -442,11 +442,11 @@ test("bare milestone id still falls back to a lone team-suffix projection (#1195
|
|
|
442
442
|
// ── #1208: flat-phase execute-task summaries resolve at the phase root ────────
|
|
443
443
|
//
|
|
444
444
|
// In flat-phase layout task summaries are written beside the plan files
|
|
445
|
-
// (phases/<phase>/T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir
|
|
446
|
-
// still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
|
|
445
|
+
// (phases/<phase>/S##-T##-SUMMARY.md), NOT under a tasks/ subdir. A tasks/ dir
|
|
446
|
+
// may still exist to hold auxiliary task-scoped gate artifacts (e.g. T01-VERIFY.json).
|
|
447
447
|
// Before the fix, the mere existence of that tasks/ dir redirected summary
|
|
448
|
-
// verification into tasks/T##-SUMMARY.md — which never exists — trapping
|
|
449
|
-
// in a false verification retry
|
|
448
|
+
// verification into tasks/S##-T##-SUMMARY.md — which never exists — trapping
|
|
449
|
+
// auto-mode in a false verification retry after a successful task.
|
|
450
450
|
|
|
451
451
|
test("flat-phase execute-task summary resolves at phase root when no tasks/ dir exists (#1208)", () => {
|
|
452
452
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-flat-task-summary-")));
|
|
@@ -461,7 +461,7 @@ test("flat-phase execute-task summary resolves at phase root when no tasks/ dir
|
|
|
461
461
|
|
|
462
462
|
assert.equal(
|
|
463
463
|
resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root),
|
|
464
|
-
join(phaseDir, "T02-SUMMARY.md"),
|
|
464
|
+
join(phaseDir, "S03-T02-SUMMARY.md"),
|
|
465
465
|
);
|
|
466
466
|
} finally {
|
|
467
467
|
_clearGsdRootCache();
|
|
@@ -489,12 +489,12 @@ test("flat-phase execute-task summary resolves at phase root even when tasks/ di
|
|
|
489
489
|
const resolved = resolveExpectedArtifactPath("execute-task", "M049/S03/T02", root);
|
|
490
490
|
assert.equal(
|
|
491
491
|
resolved,
|
|
492
|
-
join(phaseDir, "T02-SUMMARY.md"),
|
|
492
|
+
join(phaseDir, "S03-T02-SUMMARY.md"),
|
|
493
493
|
"flat-phase task summary must resolve at the phase root, not tasks/",
|
|
494
494
|
);
|
|
495
495
|
assert.notEqual(
|
|
496
496
|
resolved,
|
|
497
|
-
join(tasksDir, "T02-SUMMARY.md"),
|
|
497
|
+
join(tasksDir, "S03-T02-SUMMARY.md"),
|
|
498
498
|
"a tasks/ dir with gate artifacts must NOT redirect summary resolution into tasks/",
|
|
499
499
|
);
|
|
500
500
|
} finally {
|
|
@@ -1218,6 +1218,10 @@ test("hasImplementationArtifacts finds integration implementation-only commits w
|
|
|
1218
1218
|
|
|
1219
1219
|
execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
|
|
1220
1220
|
writeIntegrationBranch(base, "M001", "main");
|
|
1221
|
+
// ADR-045: writeIntegrationBranch no longer creates milestones/<MID>/ as a
|
|
1222
|
+
// side effect (META is now flat at .gsd/<MID>-META.json), so scaffold the
|
|
1223
|
+
// legacy summary dir explicitly.
|
|
1224
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
1221
1225
|
writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
|
|
1222
1226
|
execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
|
|
1223
1227
|
execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
|
|
@@ -2063,6 +2067,41 @@ test("#1088: writeReactiveExecuteBlocker preserves deferred batch task statuses"
|
|
|
2063
2067
|
}
|
|
2064
2068
|
});
|
|
2065
2069
|
|
|
2070
|
+
test("#1343: writeReactiveExecuteBlocker uses slice-qualified summaries in flat-phase (no cross-slice collision)", () => {
|
|
2071
|
+
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
2072
|
+
try {
|
|
2073
|
+
// Flat-phase layout: slices S01 and S02 share the phase dir and reuse task
|
|
2074
|
+
// id T03. Only the sibling slice S01 produced a summary.
|
|
2075
|
+
const phaseDir = join(base, ".gsd", "phases", "01-test");
|
|
2076
|
+
mkdirSync(phaseDir, { recursive: true });
|
|
2077
|
+
writeFileSync(join(phaseDir, "01-01-PLAN.md"), "# S01: First\n", "utf-8");
|
|
2078
|
+
writeFileSync(join(phaseDir, "01-02-PLAN.md"), "# S02: Second\n", "utf-8");
|
|
2079
|
+
writeFileSync(join(phaseDir, "S01-T03-SUMMARY.md"), "# S01 T03 Summary\n", "utf-8");
|
|
2080
|
+
|
|
2081
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
2082
|
+
insertMilestone({ id: "M001", title: "Milestone", status: "active" });
|
|
2083
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First", status: "pending" });
|
|
2084
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Second", status: "pending" });
|
|
2085
|
+
insertTask({ id: "T03", milestoneId: "M001", sliceId: "S02", title: "Shared id", status: "pending" });
|
|
2086
|
+
|
|
2087
|
+
const recovery = writeReactiveExecuteBlocker(
|
|
2088
|
+
"M001/S02/reactive+T03",
|
|
2089
|
+
base,
|
|
2090
|
+
"verification retries exhausted",
|
|
2091
|
+
);
|
|
2092
|
+
|
|
2093
|
+
assert.ok(recovery, "recovery should run with DB available");
|
|
2094
|
+
// S02 never wrote S02-T03-SUMMARY.md; the sibling S01-T03-SUMMARY.md must
|
|
2095
|
+
// NOT make S02/T03 look complete. It has to be skipped.
|
|
2096
|
+
assert.deepEqual(recovery!.completedTaskIds, []);
|
|
2097
|
+
assert.deepEqual(recovery!.skippedTaskIds, ["T03"]);
|
|
2098
|
+
assert.equal(getTask("M001", "S02", "T03")?.status, "skipped");
|
|
2099
|
+
} finally {
|
|
2100
|
+
closeDatabase();
|
|
2101
|
+
cleanup(base);
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
|
|
2066
2105
|
test("#4414: verifyExpectedArtifact parallel-research succeeds when all research-ready slices have RESEARCH", () => {
|
|
2067
2106
|
const base = makeTmpBase();
|
|
2068
2107
|
try {
|
|
@@ -143,3 +143,58 @@ test("successful shell result clears source context before provider injection",
|
|
|
143
143
|
assert.equal(payload.messages.length, 1);
|
|
144
144
|
assert.doesNotMatch(payload.messages[0].content[0].text, /Source Context Block/);
|
|
145
145
|
});
|
|
146
|
+
|
|
147
|
+
test("before_provider_request keeps only the latest GSD context injection across a 5-turn session", async (t) => {
|
|
148
|
+
const dir = mkdtempSync(join(tmpdir(), "gsd-before-provider-injections-"));
|
|
149
|
+
const gsdHome = join(dir, "home");
|
|
150
|
+
const project = join(dir, "project");
|
|
151
|
+
const previousCwd = process.cwd();
|
|
152
|
+
const previousGsdHome = process.env.GSD_HOME;
|
|
153
|
+
|
|
154
|
+
mkdirSync(join(project, ".gsd"), { recursive: true });
|
|
155
|
+
mkdirSync(gsdHome, { recursive: true });
|
|
156
|
+
process.env.GSD_HOME = gsdHome;
|
|
157
|
+
process.chdir(project);
|
|
158
|
+
_setAutoActiveForTest(false);
|
|
159
|
+
|
|
160
|
+
t.after(() => {
|
|
161
|
+
_setAutoActiveForTest(false);
|
|
162
|
+
process.chdir(previousCwd);
|
|
163
|
+
if (previousGsdHome === undefined) delete process.env.GSD_HOME;
|
|
164
|
+
else process.env.GSD_HOME = previousGsdHome;
|
|
165
|
+
rmSync(dir, { recursive: true, force: true });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const beforeProviderRequest = requireHook(createHookHandlers(), "before_provider_request");
|
|
169
|
+
|
|
170
|
+
const sentinel = "[GSD Context Injection]";
|
|
171
|
+
const contextInjection = (turn: number) => ({
|
|
172
|
+
role: "user",
|
|
173
|
+
content: [{ type: "text", text: `${sentinel}\n[MEMORY] turn=${turn}` }],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const payload = {
|
|
177
|
+
messages: [
|
|
178
|
+
{ role: "user", content: [{ type: "text", text: "turn 1" }] },
|
|
179
|
+
contextInjection(1),
|
|
180
|
+
{ role: "assistant", content: [{ type: "text", text: "r1" }] },
|
|
181
|
+
{ role: "user", content: [{ type: "text", text: "turn 2" }] },
|
|
182
|
+
contextInjection(2),
|
|
183
|
+
{ role: "assistant", content: [{ type: "text", text: "r2" }] },
|
|
184
|
+
{ role: "user", content: [{ type: "text", text: "turn 3" }] },
|
|
185
|
+
contextInjection(3),
|
|
186
|
+
{ role: "assistant", content: [{ type: "text", text: "r3" }] },
|
|
187
|
+
{ role: "user", content: [{ type: "text", text: "turn 4" }] },
|
|
188
|
+
contextInjection(4),
|
|
189
|
+
{ role: "assistant", content: [{ type: "text", text: "r4" }] },
|
|
190
|
+
{ role: "user", content: [{ type: "text", text: "turn 5" }] },
|
|
191
|
+
contextInjection(5),
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await beforeProviderRequest({ payload });
|
|
196
|
+
|
|
197
|
+
const survivors = (payload.messages as any[]).filter((m) => (m.content?.[0]?.text ?? "").startsWith(sentinel));
|
|
198
|
+
assert.equal(survivors.length, 1);
|
|
199
|
+
assert.match(survivors[0].content[0].text, /turn=5/);
|
|
200
|
+
});
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
SCHEMA_VERSION,
|
|
24
24
|
} from '../gsd-db.ts';
|
|
25
25
|
import { handleCompleteTask } from '../tools/complete-task.ts';
|
|
26
|
+
import { resolveTaskFile } from '../paths.ts';
|
|
26
27
|
|
|
27
28
|
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
|
|
28
29
|
|
|
@@ -381,6 +382,80 @@ console.log('\n=== complete-task: handler happy path ===');
|
|
|
381
382
|
cleanup(dbPath);
|
|
382
383
|
}
|
|
383
384
|
|
|
385
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
386
|
+
// complete-task: Flat-phase duplicate task IDs are slice-qualified
|
|
387
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
388
|
+
|
|
389
|
+
console.log('\n=== complete-task: flat-phase duplicate task IDs are slice-qualified ===');
|
|
390
|
+
{
|
|
391
|
+
const dbPath = tempDbPath();
|
|
392
|
+
openDatabase(dbPath);
|
|
393
|
+
|
|
394
|
+
const basePath = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-flat-task-summary-'));
|
|
395
|
+
const phaseDir = path.join(basePath, '.gsd', 'phases', '01-test');
|
|
396
|
+
fs.mkdirSync(phaseDir, { recursive: true });
|
|
397
|
+
fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), `# S01: First Slice
|
|
398
|
+
|
|
399
|
+
## Tasks
|
|
400
|
+
|
|
401
|
+
- [ ] **T03: Shared task id** \`est:30m\`
|
|
402
|
+
- Do: Complete first slice task
|
|
403
|
+
- Verify: Run first check
|
|
404
|
+
`);
|
|
405
|
+
fs.writeFileSync(path.join(phaseDir, '01-02-PLAN.md'), `# S02: Second Slice
|
|
406
|
+
|
|
407
|
+
## Tasks
|
|
408
|
+
|
|
409
|
+
- [ ] **T03: Shared task id** \`est:30m\`
|
|
410
|
+
- Do: Complete second slice task
|
|
411
|
+
- Verify: Run second check
|
|
412
|
+
`);
|
|
413
|
+
|
|
414
|
+
insertMilestone({ id: 'M001', title: 'Test Milestone' });
|
|
415
|
+
insertSlice({ id: 'S01', milestoneId: 'M001', title: 'First Slice', sequence: 1 });
|
|
416
|
+
insertSlice({ id: 'S02', milestoneId: 'M001', title: 'Second Slice', sequence: 2 });
|
|
417
|
+
|
|
418
|
+
const first = await handleCompleteTask({
|
|
419
|
+
...makeValidParams(),
|
|
420
|
+
taskId: 'T03',
|
|
421
|
+
sliceId: 'S01',
|
|
422
|
+
oneLiner: 'Completed first slice T03',
|
|
423
|
+
}, basePath);
|
|
424
|
+
const second = await handleCompleteTask({
|
|
425
|
+
...makeValidParams(),
|
|
426
|
+
taskId: 'T03',
|
|
427
|
+
sliceId: 'S02',
|
|
428
|
+
oneLiner: 'Completed second slice T03',
|
|
429
|
+
}, basePath);
|
|
430
|
+
|
|
431
|
+
assertTrue(!('error' in first), 'first duplicate task ID completion should succeed');
|
|
432
|
+
assertTrue(!('error' in second), 'second duplicate task ID completion should succeed');
|
|
433
|
+
if (!('error' in first) && !('error' in second)) {
|
|
434
|
+
const firstSummary = path.join(phaseDir, 'S01-T03-SUMMARY.md');
|
|
435
|
+
const secondSummary = path.join(phaseDir, 'S02-T03-SUMMARY.md');
|
|
436
|
+
const legacySummary = path.join(phaseDir, 'T03-SUMMARY.md');
|
|
437
|
+
|
|
438
|
+
assertEq(first.summaryPath, firstSummary, 'S01/T03 should write a slice-qualified summary path');
|
|
439
|
+
assertEq(second.summaryPath, secondSummary, 'S02/T03 should write a slice-qualified summary path');
|
|
440
|
+
assertTrue(fs.existsSync(firstSummary), 'S01/T03 summary should exist');
|
|
441
|
+
assertTrue(fs.existsSync(secondSummary), 'S02/T03 summary should exist');
|
|
442
|
+
assertTrue(!fs.existsSync(legacySummary), 'flat-phase completion should not create legacy T03-SUMMARY.md');
|
|
443
|
+
|
|
444
|
+
const firstContent = fs.readFileSync(firstSummary, 'utf-8');
|
|
445
|
+
const secondContent = fs.readFileSync(secondSummary, 'utf-8');
|
|
446
|
+
assertMatch(firstContent, /parent: S01/, 'S01/T03 summary should keep parent S01');
|
|
447
|
+
assertMatch(secondContent, /parent: S02/, 'S02/T03 summary should keep parent S02');
|
|
448
|
+
assertEq(resolveTaskFile(basePath, 'M001', 'S02', 'T03', 'SUMMARY'), secondSummary, 'resolver should prefer S02-specific summary');
|
|
449
|
+
|
|
450
|
+
const legacyFallback = path.join(phaseDir, 'T99-SUMMARY.md');
|
|
451
|
+
fs.writeFileSync(legacyFallback, '# Legacy task summary\n');
|
|
452
|
+
assertEq(resolveTaskFile(basePath, 'M001', 'S09', 'T99', 'SUMMARY'), legacyFallback, 'resolver should fall back to old flat summary names');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
cleanupDir(basePath);
|
|
456
|
+
cleanup(dbPath);
|
|
457
|
+
}
|
|
458
|
+
|
|
384
459
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
385
460
|
// complete-task: Projection failure rolls DB completion back
|
|
386
461
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -416,7 +491,7 @@ console.log('\n=== complete-task: projection failure rolls DB completion back ==
|
|
|
416
491
|
).all();
|
|
417
492
|
assertEq(evRows.length, 0, 'verification evidence should be deleted when projection rollback runs');
|
|
418
493
|
|
|
419
|
-
const summaryPath = path.join(path.dirname(planPath), 'T01-SUMMARY.md');
|
|
494
|
+
const summaryPath = path.join(path.dirname(planPath), 'S01-T01-SUMMARY.md');
|
|
420
495
|
assertTrue(!fs.existsSync(summaryPath), 'SUMMARY.md should be removed so disk state stays pending');
|
|
421
496
|
|
|
422
497
|
cleanupDir(basePath);
|