gsd-pi 2.41.0-dev.cac69f9 → 2.42.0-dev.1df898f
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 +23 -0
- package/dist/cli.js +18 -3
- package/dist/loader.js +3 -1
- package/dist/resource-loader.js +39 -6
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
- package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
- package/dist/resources/extensions/async-jobs/index.js +2 -0
- package/dist/resources/extensions/gsd/auto/loop.js +80 -0
- package/dist/resources/extensions/gsd/auto/phases.js +3 -5
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
- package/dist/resources/extensions/gsd/auto-start.js +8 -11
- package/dist/resources/extensions/gsd/auto.js +28 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +32 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
- package/dist/resources/extensions/gsd/context-injector.js +74 -0
- package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
- package/dist/resources/extensions/gsd/custom-verification.js +145 -0
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
- package/dist/resources/extensions/gsd/definition-loader.js +352 -0
- package/dist/resources/extensions/gsd/detection.js +19 -0
- package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
- package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
- package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
- package/dist/resources/extensions/gsd/engine-types.js +8 -0
- package/dist/resources/extensions/gsd/execution-policy.js +8 -0
- package/dist/resources/extensions/gsd/forensics.js +84 -0
- package/dist/resources/extensions/gsd/git-constants.js +1 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/graph.js +225 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +59 -8
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/repo-identity.js +46 -5
- package/dist/resources/extensions/gsd/run-manager.js +134 -0
- package/dist/resources/extensions/gsd/service-tier.js +13 -4
- package/dist/resources/extensions/gsd/session-lock.js +2 -2
- package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
- package/dist/resources/extensions/gsd/worktree.js +2 -2
- package/dist/resources/extensions/mcp-client/index.js +2 -1
- package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
- package/dist/resources/skills/create-workflow/SKILL.md +103 -0
- package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- 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 +2 -2
- 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/api/git/route.js +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 +15 -15
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +40 -4
- package/package.json +1 -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/dist/types.d.ts +6 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +53 -0
- package/packages/pi-agent-core/src/agent.ts +3 -0
- package/packages/pi-agent-core/src/types.ts +6 -0
- package/packages/pi-agent-core/tsconfig.json +1 -1
- package/packages/pi-ai/dist/models.d.ts +5 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +1135 -1588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +1543 -0
- package/packages/pi-ai/src/models.generated.ts +1140 -1593
- package/packages/pi-ai/src/models.ts +7 -4
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
- package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
- package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
- package/src/resources/extensions/async-jobs/index.ts +1 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
- package/src/resources/extensions/gsd/auto/loop.ts +91 -0
- package/src/resources/extensions/gsd/auto/phases.ts +3 -5
- package/src/resources/extensions/gsd/auto/session.ts +6 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
- package/src/resources/extensions/gsd/auto-start.ts +7 -10
- package/src/resources/extensions/gsd/auto.ts +31 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
- package/src/resources/extensions/gsd/context-injector.ts +100 -0
- package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
- package/src/resources/extensions/gsd/custom-verification.ts +180 -0
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
- package/src/resources/extensions/gsd/definition-loader.ts +462 -0
- package/src/resources/extensions/gsd/detection.ts +19 -0
- package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
- package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
- package/src/resources/extensions/gsd/engine-types.ts +71 -0
- package/src/resources/extensions/gsd/execution-policy.ts +43 -0
- package/src/resources/extensions/gsd/forensics.ts +92 -0
- package/src/resources/extensions/gsd/git-constants.ts +1 -0
- package/src/resources/extensions/gsd/git-service.ts +0 -1
- package/src/resources/extensions/gsd/gitignore.ts +1 -1
- package/src/resources/extensions/gsd/graph.ts +312 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +62 -6
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/repo-identity.ts +48 -5
- package/src/resources/extensions/gsd/run-manager.ts +180 -0
- package/src/resources/extensions/gsd/service-tier.ts +17 -4
- package/src/resources/extensions/gsd/session-lock.ts +2 -2
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
- package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
- package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
- package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
- package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
- package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
- package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
- package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
- package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
- package/src/resources/extensions/gsd/worktree.ts +2 -2
- package/src/resources/extensions/mcp-client/index.ts +5 -1
- package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
- package/src/resources/skills/create-workflow/SKILL.md +103 -0
- package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, sep } from "node:path";
|
|
3
|
-
import { readRepoMeta, externalProjectsRoot } from "./repo-identity.js";
|
|
3
|
+
import { readRepoMeta, externalProjectsRoot, cleanNumberedGsdVariants } from "./repo-identity.js";
|
|
4
4
|
import { loadFile, parseRoadmap } from "./files.js";
|
|
5
5
|
import { resolveMilestoneFile, milestonesDir, gsdRoot, resolveGsdRootFile } from "./paths.js";
|
|
6
6
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
@@ -733,6 +733,36 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
|
|
|
733
733
|
catch {
|
|
734
734
|
// Non-fatal — external state check failed
|
|
735
735
|
}
|
|
736
|
+
// ── Numbered .gsd collision variants (#2205) ───────────────────────────
|
|
737
|
+
// macOS APFS can create ".gsd 2", ".gsd 3" etc. when a directory blocks
|
|
738
|
+
// symlink creation. These must be removed so the canonical .gsd is used.
|
|
739
|
+
try {
|
|
740
|
+
const variantPattern = /^\.gsd \d+$/;
|
|
741
|
+
const entries = readdirSync(basePath);
|
|
742
|
+
const variants = entries.filter(e => variantPattern.test(e));
|
|
743
|
+
if (variants.length > 0) {
|
|
744
|
+
for (const v of variants) {
|
|
745
|
+
issues.push({
|
|
746
|
+
severity: "warning",
|
|
747
|
+
code: "numbered_gsd_variant",
|
|
748
|
+
scope: "project",
|
|
749
|
+
unitId: "project",
|
|
750
|
+
message: `Found macOS collision variant "${v}" — this can cause GSD state to appear deleted.`,
|
|
751
|
+
file: v,
|
|
752
|
+
fixable: true,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (shouldFix("numbered_gsd_variant")) {
|
|
756
|
+
const removed = cleanNumberedGsdVariants(basePath);
|
|
757
|
+
for (const name of removed) {
|
|
758
|
+
fixesApplied.push(`removed numbered .gsd variant: ${name}`);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
// Non-fatal — variant check failed
|
|
765
|
+
}
|
|
736
766
|
// ── Metrics ledger integrity ───────────────────────────────────────────
|
|
737
767
|
try {
|
|
738
768
|
const metricsPath = join(root, "metrics.json");
|
|
@@ -260,11 +260,21 @@ function checkRemoteQuestionsProvider() {
|
|
|
260
260
|
function checkOptionalProviders() {
|
|
261
261
|
const optional = ["brave", "tavily", "jina", "context7"];
|
|
262
262
|
const results = [];
|
|
263
|
+
// Determine which search providers are configured so we can suppress
|
|
264
|
+
// "not configured" noise for alternative search providers when at least
|
|
265
|
+
// one is already active (e.g. don't warn about missing BRAVE_API_KEY
|
|
266
|
+
// when Tavily is configured).
|
|
267
|
+
const searchProviderIds = ["brave", "tavily"];
|
|
268
|
+
const hasAnySearchProvider = searchProviderIds.some(id => resolveKey(id).found);
|
|
263
269
|
for (const providerId of optional) {
|
|
264
270
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
265
271
|
if (!info)
|
|
266
272
|
continue;
|
|
267
273
|
const lookup = resolveKey(providerId);
|
|
274
|
+
// Skip unconfigured search providers when another search provider is active
|
|
275
|
+
if (!lookup.found && hasAnySearchProvider && info.category === "search") {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
268
278
|
results.push({
|
|
269
279
|
name: providerId,
|
|
270
280
|
label: info.label,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-resolver.ts — Route sessions to engine/policy pairs.
|
|
3
|
+
*
|
|
4
|
+
* Routes `null` and `"dev"` engine IDs to the DevWorkflowEngine/DevExecutionPolicy
|
|
5
|
+
* pair. Any other non-null engine ID is treated as a custom workflow engine that
|
|
6
|
+
* reads its state from an `activeRunDir`. Respects `GSD_ENGINE_BYPASS=1` kill
|
|
7
|
+
* switch to skip the engine layer entirely.
|
|
8
|
+
*/
|
|
9
|
+
import { DevWorkflowEngine } from "./dev-workflow-engine.js";
|
|
10
|
+
import { DevExecutionPolicy } from "./dev-execution-policy.js";
|
|
11
|
+
import { CustomWorkflowEngine } from "./custom-workflow-engine.js";
|
|
12
|
+
import { CustomExecutionPolicy } from "./custom-execution-policy.js";
|
|
13
|
+
/**
|
|
14
|
+
* Resolve an engine/policy pair for the given session.
|
|
15
|
+
*
|
|
16
|
+
* - `null` or `"dev"` → DevWorkflowEngine + DevExecutionPolicy
|
|
17
|
+
* - any other non-null ID → CustomWorkflowEngine(activeRunDir) + CustomExecutionPolicy()
|
|
18
|
+
* (requires activeRunDir to be a non-empty string)
|
|
19
|
+
*
|
|
20
|
+
* Note: `GSD_ENGINE_BYPASS=1` is checked in autoLoop before calling this function.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveEngine(session) {
|
|
23
|
+
const { activeEngineId, activeRunDir } = session;
|
|
24
|
+
if (activeEngineId === null || activeEngineId === "dev") {
|
|
25
|
+
return {
|
|
26
|
+
engine: new DevWorkflowEngine(),
|
|
27
|
+
policy: new DevExecutionPolicy(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Any non-null, non-"dev" engine ID is a custom workflow engine.
|
|
31
|
+
// activeRunDir is required — the engine reads GRAPH.yaml from it.
|
|
32
|
+
if (!activeRunDir || typeof activeRunDir !== "string") {
|
|
33
|
+
throw new Error(`Custom engine "${activeEngineId}" requires activeRunDir to be a non-empty string, ` +
|
|
34
|
+
`got: ${JSON.stringify(activeRunDir)}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
engine: new CustomWorkflowEngine(activeRunDir),
|
|
38
|
+
policy: new CustomExecutionPolicy(activeRunDir),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-types.ts — Engine-polymorphic type contracts.
|
|
3
|
+
*
|
|
4
|
+
* LEAF NODE: This file must have ZERO imports from any GSD module.
|
|
5
|
+
* Only `node:` imports are permitted. All engine/policy interfaces
|
|
6
|
+
* depend on these types; nothing here depends on GSD internals.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -24,6 +24,70 @@ import { loadPrompt } from "./prompt-loader.js";
|
|
|
24
24
|
import { gsdRoot } from "./paths.js";
|
|
25
25
|
import { formatDuration } from "../shared/format-utils.js";
|
|
26
26
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
27
|
+
import { loadEffectiveGSDPreferences, loadGlobalGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
|
|
28
|
+
import { showNextAction } from "../shared/tui.js";
|
|
29
|
+
import { ensurePreferencesFile, serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
|
30
|
+
// ─── Duplicate Detection ──────────────────────────────────────────────────────
|
|
31
|
+
const DEDUP_PROMPT_SECTION = `
|
|
32
|
+
## Duplicate Detection (REQUIRED before issue creation)
|
|
33
|
+
|
|
34
|
+
Before offering to create a GitHub issue, you MUST search for existing issues and PRs that may already address this bug. This step uses the user's AI tokens for analysis.
|
|
35
|
+
|
|
36
|
+
### Search Steps
|
|
37
|
+
|
|
38
|
+
1. **Search closed issues** for similar keywords from your diagnosis:
|
|
39
|
+
\`\`\`
|
|
40
|
+
gh issue list --repo gsd-build/gsd-2 --state closed --search "<keywords from root cause>" --limit 20
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
2. **Search open PRs** that might contain the fix:
|
|
44
|
+
\`\`\`
|
|
45
|
+
gh pr list --repo gsd-build/gsd-2 --state open --search "<keywords>" --limit 10
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
3. **Search merged PRs** that may have already fixed this:
|
|
49
|
+
\`\`\`
|
|
50
|
+
gh pr list --repo gsd-build/gsd-2 --state merged --search "<keywords>" --limit 10
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
### Analysis
|
|
54
|
+
|
|
55
|
+
For each result, compare it against your root-cause diagnosis:
|
|
56
|
+
- Does the issue describe the same code path or file?
|
|
57
|
+
- Does the PR modify the same file:line you identified?
|
|
58
|
+
- Is the symptom description semantically similar even if keywords differ?
|
|
59
|
+
|
|
60
|
+
### Present Findings
|
|
61
|
+
|
|
62
|
+
If you find potential matches, present them to the user:
|
|
63
|
+
|
|
64
|
+
1. **"Already fixed by PR #X — skip issue creation"** — when a merged PR or closed issue clearly addresses the same root cause. Explain why you believe it matches.
|
|
65
|
+
2. **"Add my findings to existing issue #Y"** — when an open issue exists for the same bug. Use \`gh issue comment #Y --repo gsd-build/gsd-2\` to add forensic evidence.
|
|
66
|
+
3. **"Create new issue anyway"** — when existing results do not cover this specific failure.
|
|
67
|
+
|
|
68
|
+
Only proceed to issue creation if no matches were found OR the user explicitly chooses "Create new issue anyway".
|
|
69
|
+
`;
|
|
70
|
+
async function writeForensicsDedupPref(ctx, enabled) {
|
|
71
|
+
const prefsPath = getGlobalGSDPreferencesPath();
|
|
72
|
+
await ensurePreferencesFile(prefsPath, ctx, "global");
|
|
73
|
+
const existing = loadGlobalGSDPreferences();
|
|
74
|
+
const prefs = existing?.preferences ? { ...existing.preferences } : {};
|
|
75
|
+
prefs.version = prefs.version || 1;
|
|
76
|
+
prefs.forensics_dedup = enabled;
|
|
77
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
78
|
+
const raw = existsSync(prefsPath) ? readFileSync(prefsPath, "utf-8") : "";
|
|
79
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
80
|
+
const start = raw.startsWith("---\n") ? 4 : raw.startsWith("---\r\n") ? 5 : -1;
|
|
81
|
+
if (start !== -1) {
|
|
82
|
+
const closingIdx = raw.indexOf("\n---", start);
|
|
83
|
+
if (closingIdx !== -1) {
|
|
84
|
+
const after = raw.slice(closingIdx + 4);
|
|
85
|
+
if (after.trim())
|
|
86
|
+
body = after;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(prefsPath, `---\n${frontmatter}---${body}`, "utf-8");
|
|
90
|
+
}
|
|
27
91
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
28
92
|
export async function handleForensics(args, ctx, pi) {
|
|
29
93
|
if (isAutoActive()) {
|
|
@@ -44,6 +108,25 @@ export async function handleForensics(args, ctx, pi) {
|
|
|
44
108
|
ctx.ui.notify("Problem description required for forensic analysis.", "warning");
|
|
45
109
|
return;
|
|
46
110
|
}
|
|
111
|
+
// ─── Duplicate detection opt-in ─────────────────────────────────────────────
|
|
112
|
+
const effectivePrefs = loadEffectiveGSDPreferences()?.preferences;
|
|
113
|
+
let dedupEnabled = effectivePrefs?.forensics_dedup === true;
|
|
114
|
+
if (effectivePrefs?.forensics_dedup === undefined) {
|
|
115
|
+
const choice = await showNextAction(ctx, {
|
|
116
|
+
title: "Duplicate detection available",
|
|
117
|
+
summary: ["Before filing a GitHub issue, forensics can search existing issues and PRs to avoid duplicates.", "This uses additional AI tokens for analysis."],
|
|
118
|
+
actions: [
|
|
119
|
+
{ id: "enable", label: "Enable duplicate detection", description: "Search issues/PRs before filing (recommended)", recommended: true },
|
|
120
|
+
{ id: "skip", label: "Skip for now", description: "File without checking for duplicates" },
|
|
121
|
+
],
|
|
122
|
+
notYetMessage: "You can enable this later via preferences (forensics_dedup: true).",
|
|
123
|
+
});
|
|
124
|
+
if (choice === "enable") {
|
|
125
|
+
await writeForensicsDedupPref(ctx, true);
|
|
126
|
+
dedupEnabled = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const dedupSection = dedupEnabled ? DEDUP_PROMPT_SECTION : "";
|
|
47
130
|
ctx.ui.notify("Building forensic report...", "info");
|
|
48
131
|
const report = await buildForensicReport(basePath);
|
|
49
132
|
const savedPath = saveForensicReport(basePath, report, problemDescription);
|
|
@@ -61,6 +144,7 @@ export async function handleForensics(args, ctx, pi) {
|
|
|
61
144
|
problemDescription,
|
|
62
145
|
forensicData,
|
|
63
146
|
gsdSourceDir,
|
|
147
|
+
dedupSection,
|
|
64
148
|
});
|
|
65
149
|
ctx.ui.notify(`Forensic report saved: ${relative(basePath, savedPath)}`, "info");
|
|
66
150
|
pi.sendMessage({ customType: "gsd-forensics", content, display: false }, { triggerTurn: true });
|
|
@@ -130,7 +130,7 @@ export function readIntegrationBranch(basePath, milestoneId) {
|
|
|
130
130
|
*/
|
|
131
131
|
/** Regex matching GSD quick-task branches: gsd/quick/<num>-<slug> */
|
|
132
132
|
export const QUICK_BRANCH_RE = /^gsd\/quick\//;
|
|
133
|
-
export function writeIntegrationBranch(basePath, milestoneId, branch
|
|
133
|
+
export function writeIntegrationBranch(basePath, milestoneId, branch) {
|
|
134
134
|
// Don't record slice branches as the integration target
|
|
135
135
|
if (SLICE_BRANCH_RE.test(branch))
|
|
136
136
|
return;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* graph.ts — Pure data module for GRAPH.yaml workflow step tracking.
|
|
3
|
+
*
|
|
4
|
+
* Provides types and functions for reading, writing, and querying the
|
|
5
|
+
* step graph that drives CustomWorkflowEngine. Zero engine dependencies.
|
|
6
|
+
*
|
|
7
|
+
* GRAPH.yaml lives in a run directory and tracks step statuses
|
|
8
|
+
* (pending → active → complete) with optional dependency edges.
|
|
9
|
+
*
|
|
10
|
+
* Observability:
|
|
11
|
+
* - readGraph/writeGraph use YAML on disk — human-readable, diffable,
|
|
12
|
+
* inspectable with `cat` or any YAML viewer.
|
|
13
|
+
* - Each GraphStep has status, startedAt, finishedAt fields visible in GRAPH.yaml.
|
|
14
|
+
* - writeGraph uses atomic write (tmp + rename) for crash safety.
|
|
15
|
+
* - All operations are immutable — callers always get a new graph object.
|
|
16
|
+
*/
|
|
17
|
+
import { parse, stringify } from "yaml";
|
|
18
|
+
import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
// ─── YAML schema mapping ─────────────────────────────────────────────────
|
|
21
|
+
const GRAPH_FILENAME = "GRAPH.yaml";
|
|
22
|
+
// ─── Functions ───────────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Read and parse GRAPH.yaml from a run directory.
|
|
25
|
+
*
|
|
26
|
+
* @param runDir — directory containing GRAPH.yaml
|
|
27
|
+
* @returns Parsed workflow graph
|
|
28
|
+
* @throws Error if file doesn't exist or YAML is malformed
|
|
29
|
+
*/
|
|
30
|
+
export function readGraph(runDir) {
|
|
31
|
+
const filePath = join(runDir, GRAPH_FILENAME);
|
|
32
|
+
if (!existsSync(filePath)) {
|
|
33
|
+
throw new Error(`GRAPH.yaml not found: ${filePath}`);
|
|
34
|
+
}
|
|
35
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
36
|
+
const yaml = parse(raw);
|
|
37
|
+
if (!yaml?.steps || !Array.isArray(yaml.steps)) {
|
|
38
|
+
throw new Error(`Invalid GRAPH.yaml: missing or invalid 'steps' array in ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
steps: yaml.steps.map((s) => ({
|
|
42
|
+
id: s.id,
|
|
43
|
+
title: s.title,
|
|
44
|
+
status: s.status,
|
|
45
|
+
prompt: s.prompt,
|
|
46
|
+
dependsOn: s.depends_on ?? [],
|
|
47
|
+
...(s.parent_step_id != null ? { parentStepId: s.parent_step_id } : {}),
|
|
48
|
+
...(s.started_at != null ? { startedAt: s.started_at } : {}),
|
|
49
|
+
...(s.finished_at != null ? { finishedAt: s.finished_at } : {}),
|
|
50
|
+
})),
|
|
51
|
+
metadata: {
|
|
52
|
+
name: yaml.metadata?.name ?? "unnamed",
|
|
53
|
+
createdAt: yaml.metadata?.created_at ?? new Date().toISOString(),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Write a workflow graph to GRAPH.yaml in a run directory.
|
|
59
|
+
* Creates the directory if it doesn't exist. Write is atomic (write + rename).
|
|
60
|
+
*
|
|
61
|
+
* @param runDir — directory to write GRAPH.yaml into
|
|
62
|
+
* @param graph — the workflow graph to serialize
|
|
63
|
+
*/
|
|
64
|
+
export function writeGraph(runDir, graph) {
|
|
65
|
+
if (!existsSync(runDir)) {
|
|
66
|
+
mkdirSync(runDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
const yamlData = {
|
|
69
|
+
steps: graph.steps.map((s) => ({
|
|
70
|
+
id: s.id,
|
|
71
|
+
title: s.title,
|
|
72
|
+
status: s.status,
|
|
73
|
+
prompt: s.prompt,
|
|
74
|
+
depends_on: s.dependsOn.length > 0 ? s.dependsOn : undefined,
|
|
75
|
+
parent_step_id: s.parentStepId ?? undefined,
|
|
76
|
+
started_at: s.startedAt ?? undefined,
|
|
77
|
+
finished_at: s.finishedAt ?? undefined,
|
|
78
|
+
})),
|
|
79
|
+
metadata: {
|
|
80
|
+
name: graph.metadata.name,
|
|
81
|
+
created_at: graph.metadata.createdAt,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const filePath = join(runDir, GRAPH_FILENAME);
|
|
85
|
+
const tmpPath = filePath + ".tmp";
|
|
86
|
+
const content = stringify(yamlData);
|
|
87
|
+
writeFileSync(tmpPath, content, "utf-8");
|
|
88
|
+
// Atomic rename for crash safety
|
|
89
|
+
renameSync(tmpPath, filePath);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get the next pending step whose dependencies are all complete.
|
|
93
|
+
*
|
|
94
|
+
* Returns the first step (in array order) with status "pending" where
|
|
95
|
+
* every step in its `dependsOn` list has status "complete".
|
|
96
|
+
*
|
|
97
|
+
* @param graph — the workflow graph to query
|
|
98
|
+
* @returns The next dispatchable step, or null if none available
|
|
99
|
+
*/
|
|
100
|
+
export function getNextPendingStep(graph) {
|
|
101
|
+
const statusMap = new Map(graph.steps.map((s) => [s.id, s.status]));
|
|
102
|
+
for (const step of graph.steps) {
|
|
103
|
+
if (step.status !== "pending")
|
|
104
|
+
continue;
|
|
105
|
+
const depsComplete = step.dependsOn.every((depId) => statusMap.get(depId) === "complete");
|
|
106
|
+
if (depsComplete)
|
|
107
|
+
return step;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Return a new graph with the specified step marked as "complete".
|
|
113
|
+
* Immutable — does not mutate the input graph.
|
|
114
|
+
*
|
|
115
|
+
* @param graph — the current workflow graph
|
|
116
|
+
* @param stepId — ID of the step to mark complete
|
|
117
|
+
* @returns New graph with the step's status set to "complete"
|
|
118
|
+
* @throws Error if stepId is not found in the graph
|
|
119
|
+
*/
|
|
120
|
+
export function markStepComplete(graph, stepId) {
|
|
121
|
+
const found = graph.steps.some((s) => s.id === stepId);
|
|
122
|
+
if (!found) {
|
|
123
|
+
throw new Error(`Step not found: ${stepId}`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...graph,
|
|
127
|
+
steps: graph.steps.map((s) => s.id === stepId
|
|
128
|
+
? { ...s, status: "complete", finishedAt: new Date().toISOString() }
|
|
129
|
+
: s),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// ─── Iteration expansion ─────────────────────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* Expand an iterate step into concrete instances. Pure and deterministic —
|
|
135
|
+
* identical inputs always produce identical output.
|
|
136
|
+
*
|
|
137
|
+
* Given a parent step with status "pending" and an array of matched items,
|
|
138
|
+
* creates one instance step per item, marks the parent as "expanded", and
|
|
139
|
+
* rewrites any downstream dependsOn references from the parent ID to the
|
|
140
|
+
* full set of instance IDs.
|
|
141
|
+
*
|
|
142
|
+
* @param graph — the current workflow graph (not mutated)
|
|
143
|
+
* @param stepId — ID of the iterate step to expand
|
|
144
|
+
* @param items — matched items from the source artifact
|
|
145
|
+
* @param promptTemplate — template with {{item}} placeholders
|
|
146
|
+
* @returns New WorkflowGraph with instances inserted and deps rewritten
|
|
147
|
+
* @throws Error if stepId not found or step is not pending
|
|
148
|
+
*/
|
|
149
|
+
export function expandIteration(graph, stepId, items, promptTemplate) {
|
|
150
|
+
const parentIndex = graph.steps.findIndex((s) => s.id === stepId);
|
|
151
|
+
if (parentIndex === -1) {
|
|
152
|
+
throw new Error(`expandIteration: step not found: ${stepId}`);
|
|
153
|
+
}
|
|
154
|
+
const parentStep = graph.steps[parentIndex];
|
|
155
|
+
if (parentStep.status !== "pending") {
|
|
156
|
+
throw new Error(`expandIteration: step "${stepId}" has status "${parentStep.status}", expected "pending"`);
|
|
157
|
+
}
|
|
158
|
+
// Create instance steps
|
|
159
|
+
const instanceIds = [];
|
|
160
|
+
const instances = items.map((item, i) => {
|
|
161
|
+
const instanceId = `${stepId}--${String(i + 1).padStart(3, "0")}`;
|
|
162
|
+
instanceIds.push(instanceId);
|
|
163
|
+
return {
|
|
164
|
+
id: instanceId,
|
|
165
|
+
title: `${parentStep.title}: ${item}`,
|
|
166
|
+
status: "pending",
|
|
167
|
+
prompt: promptTemplate.replace(/\{\{item\}\}/g, () => item),
|
|
168
|
+
dependsOn: [...parentStep.dependsOn],
|
|
169
|
+
parentStepId: stepId,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
// Build new steps array: copy everything, mark parent as expanded,
|
|
173
|
+
// insert instances right after the parent, rewrite downstream deps.
|
|
174
|
+
const newSteps = [];
|
|
175
|
+
for (let i = 0; i < graph.steps.length; i++) {
|
|
176
|
+
if (i === parentIndex) {
|
|
177
|
+
// Mark parent as expanded
|
|
178
|
+
newSteps.push({ ...parentStep, status: "expanded" });
|
|
179
|
+
// Insert instances immediately after parent
|
|
180
|
+
newSteps.push(...instances);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const step = graph.steps[i];
|
|
184
|
+
// Rewrite dependsOn: replace parent ID with all instance IDs
|
|
185
|
+
const hasDep = step.dependsOn.includes(stepId);
|
|
186
|
+
if (hasDep) {
|
|
187
|
+
const rewritten = step.dependsOn.flatMap((dep) => dep === stepId ? instanceIds : [dep]);
|
|
188
|
+
newSteps.push({ ...step, dependsOn: rewritten });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
newSteps.push(step);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
...graph,
|
|
197
|
+
steps: newSteps,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// ─── Definition → Graph conversion ──────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* Convert a parsed WorkflowDefinition into a WorkflowGraph with all
|
|
203
|
+
* steps in "pending" status. Used by run-manager to generate the initial
|
|
204
|
+
* GRAPH.yaml for a new run.
|
|
205
|
+
*
|
|
206
|
+
* @param def — a validated WorkflowDefinition from definition-loader
|
|
207
|
+
* @returns WorkflowGraph with pending steps and metadata from the definition
|
|
208
|
+
*/
|
|
209
|
+
export function initializeGraph(def) {
|
|
210
|
+
return {
|
|
211
|
+
steps: def.steps.map((s) => ({
|
|
212
|
+
id: s.id,
|
|
213
|
+
title: s.name,
|
|
214
|
+
status: "pending",
|
|
215
|
+
prompt: s.prompt,
|
|
216
|
+
dependsOn: s.requires ?? [],
|
|
217
|
+
})),
|
|
218
|
+
metadata: {
|
|
219
|
+
name: def.name,
|
|
220
|
+
createdAt: new Date().toISOString(),
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** @deprecated Use initializeGraph instead. Kept for backward compatibility. */
|
|
225
|
+
export { initializeGraph as graphFromDefinition };
|
|
@@ -129,14 +129,21 @@ function loadPreferencesFile(path, scope) {
|
|
|
129
129
|
export function parsePreferencesMarkdown(content) {
|
|
130
130
|
// Use indexOf instead of [\s\S]*? regex to avoid backtracking (#468)
|
|
131
131
|
const startMarker = content.startsWith('---\r\n') ? '---\r\n' : '---\n';
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
if (content.startsWith(startMarker)) {
|
|
133
|
+
const searchStart = startMarker.length;
|
|
134
|
+
const endIdx = content.indexOf('\n---', searchStart);
|
|
135
|
+
if (endIdx === -1)
|
|
136
|
+
return null;
|
|
137
|
+
const block = content.slice(searchStart, endIdx);
|
|
138
|
+
return parseFrontmatterBlock(block.replace(/\r/g, ''));
|
|
139
|
+
}
|
|
140
|
+
// Fallback: heading+list format (e.g. "## Git\n- isolation: none") (#2036)
|
|
141
|
+
// GSD agents may write preferences files without frontmatter delimiters.
|
|
142
|
+
if (/^##\s+\w/m.test(content)) {
|
|
143
|
+
return parseHeadingListFormat(content);
|
|
144
|
+
}
|
|
145
|
+
console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
|
|
146
|
+
return null;
|
|
140
147
|
}
|
|
141
148
|
function parseFrontmatterBlock(frontmatter) {
|
|
142
149
|
try {
|
|
@@ -151,6 +158,49 @@ function parseFrontmatterBlock(frontmatter) {
|
|
|
151
158
|
return {};
|
|
152
159
|
}
|
|
153
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Parse heading+list format into a nested object, then cast to GSDPreferences.
|
|
163
|
+
* Handles markdown like:
|
|
164
|
+
* ## Git
|
|
165
|
+
* - isolation: none
|
|
166
|
+
* - commit_docs: true
|
|
167
|
+
* ## Models
|
|
168
|
+
* - planner: sonnet
|
|
169
|
+
*/
|
|
170
|
+
function parseHeadingListFormat(content) {
|
|
171
|
+
const result = {};
|
|
172
|
+
let currentSection = null;
|
|
173
|
+
for (const rawLine of content.split('\n')) {
|
|
174
|
+
const line = rawLine.replace(/\r$/, '');
|
|
175
|
+
const headingMatch = line.match(/^##\s+(.+)$/);
|
|
176
|
+
if (headingMatch) {
|
|
177
|
+
currentSection = headingMatch[1].trim().toLowerCase().replace(/\s+/g, '_');
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (currentSection) {
|
|
181
|
+
const itemMatch = line.match(/^-\s+([^:]+):\s*(.*)$/);
|
|
182
|
+
if (itemMatch) {
|
|
183
|
+
if (!result[currentSection])
|
|
184
|
+
result[currentSection] = {};
|
|
185
|
+
const value = itemMatch[2].trim();
|
|
186
|
+
// Coerce "true"/"false" strings and numbers
|
|
187
|
+
result[currentSection][itemMatch[1].trim()] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Convert string values to appropriate types via YAML parser for each section
|
|
192
|
+
const typed = {};
|
|
193
|
+
for (const [section, entries] of Object.entries(result)) {
|
|
194
|
+
const yamlLines = Object.entries(entries).map(([k, v]) => `${k}: ${v}`).join('\n');
|
|
195
|
+
try {
|
|
196
|
+
typed[section] = parseYaml(yamlLines);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
typed[section] = entries;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return typed;
|
|
203
|
+
}
|
|
154
204
|
// ─── Merging ────────────────────────────────────────────────────────────────
|
|
155
205
|
/**
|
|
156
206
|
* Apply mode defaults as the lowest-priority layer.
|
|
@@ -215,6 +265,7 @@ function mergePreferences(base, override) {
|
|
|
215
265
|
? { ...(base.github ?? {}), ...(override.github ?? {}) }
|
|
216
266
|
: undefined,
|
|
217
267
|
service_tier: override.service_tier ?? base.service_tier,
|
|
268
|
+
forensics_dedup: override.forensics_dedup ?? base.forensics_dedup,
|
|
218
269
|
};
|
|
219
270
|
}
|
|
220
271
|
function mergeStringLists(base, override) {
|
|
@@ -101,11 +101,19 @@ Explain your findings:
|
|
|
101
101
|
- **Code snippet** — the problematic code and what it should do instead
|
|
102
102
|
- **Recovery** — what the user can do right now to get unstuck
|
|
103
103
|
|
|
104
|
+
{{dedupSection}}
|
|
105
|
+
|
|
104
106
|
Then **offer GitHub issue creation**: "Would you like me to create a GitHub issue for this on gsd-build/gsd-2?"
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
**CRITICAL: The `github_issues` tool ONLY targets the current user's repository — it has no `repo` parameter. You MUST use `gh issue create --repo gsd-build/gsd-2` via the `bash` tool to file on the correct repo. Do NOT use the `github_issues` tool for this.**
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
If yes, create using the `bash` tool:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
gh issue create --repo gsd-build/gsd-2 \
|
|
114
|
+
--title "..." \
|
|
115
|
+
--label "bug" --label "auto-generated" \
|
|
116
|
+
--body "$(cat <<'EOF'
|
|
109
117
|
## Problem
|
|
110
118
|
[1-2 sentence summary]
|
|
111
119
|
|
|
@@ -128,11 +136,10 @@ If yes, create using `gh issue create` with this format:
|
|
|
128
136
|
|
|
129
137
|
---
|
|
130
138
|
*Auto-generated by `/gsd forensics`*
|
|
139
|
+
EOF
|
|
140
|
+
)"
|
|
131
141
|
```
|
|
132
142
|
|
|
133
|
-
**Repository:** gsd-build/gsd-2
|
|
134
|
-
**Labels:** bug, auto-generated
|
|
135
|
-
|
|
136
143
|
### Redaction Rules (CRITICAL)
|
|
137
144
|
|
|
138
145
|
Before creating the issue, you MUST:
|