@mmerterden/multi-agent-pipeline 14.2.1 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -6
- package/README.md +20 -9
- package/README.tr.md +150 -0
- package/docs/FIGMA_PIPELINE.md +3 -3
- package/docs/adr/0006-skills-core-external-split.md +1 -1
- package/docs/adr/0009-claude-stack-skills-plugin-only.md +31 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +24 -9
- package/docs/ecosystem.md +237 -0
- package/docs/features.md +5 -5
- package/index.js +2 -0
- package/install/_codex-agents.mjs +11 -2
- package/install/_common.mjs +65 -1
- package/install/_dev-only-files.mjs +0 -1
- package/install/_platform-filter.mjs +73 -7
- package/install/_plugin-skills.mjs +33 -8
- package/install/claude.mjs +144 -59
- package/install/codex.mjs +37 -7
- package/install/copilot.mjs +36 -11
- package/install/index.mjs +6 -2
- package/install/templates/codex-instructions.md +1 -1
- package/install/templates/copilot-instructions.md +15 -12
- package/package.json +1 -2
- package/pipeline/commands/multi-agent/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/analysis/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +9 -9
- package/pipeline/commands/multi-agent/channels/SKILL.md +16 -5
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +186 -0
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/help/SKILL.md +19 -4
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +81 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/{ship → resume-local}/SKILL.md +13 -9
- package/pipeline/commands/multi-agent/setup/SKILL.md +5 -5
- package/pipeline/commands/multi-agent/stack/SKILL.md +55 -43
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +3 -3
- package/pipeline/commands/multi-agent/sync/SKILL.md +18 -11
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +2 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
- package/pipeline/lib/extract-conventions.sh +44 -15
- package/pipeline/lib/fetch-figma-annotations.sh +8 -1
- package/pipeline/lib/fetch-fortify.sh +23 -8
- package/pipeline/lib/figma-screenshot.sh +11 -1
- package/pipeline/lib/issue-fetcher.sh +77 -10
- package/pipeline/lib/md2confluence-v3.py +16 -2
- package/pipeline/lib/parse-complaints.sh +306 -0
- package/pipeline/lib/plan-todos.sh +5 -2
- package/pipeline/lib/post-pr-review.sh +8 -6
- package/pipeline/lib/shadow-git.sh +50 -9
- package/pipeline/lib/submodule-detector.sh +8 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +13 -2
- package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
- package/pipeline/multi-agent-refs/channels/pr.md +20 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +4 -4
- package/pipeline/multi-agent-refs/complaint-analysis-template.md +99 -0
- package/pipeline/multi-agent-refs/component-dispatch.md +6 -6
- package/pipeline/multi-agent-refs/cross-cli-contract.md +16 -16
- package/pipeline/multi-agent-refs/features/external-context-injection.md +1 -1
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +5 -5
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/generate-issue.md +3 -3
- package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
- package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
- package/pipeline/multi-agent-refs/phases/modes.md +20 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +2 -2
- package/pipeline/multi-agent-refs/phases/phase-1-analysis.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-2-planning.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +3 -3
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +12 -12
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
- package/pipeline/multi-agent-refs/phases.md +6 -0
- package/pipeline/multi-agent-refs/rules.md +2 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +1 -1
- package/pipeline/multi-agent-refs/wiki-capture.md +2 -2
- package/pipeline/preferences-template.json +13 -5
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +1 -1
- package/pipeline/schemas/complaint-analysis-spec.schema.json +216 -0
- package/pipeline/schemas/migrations/prefs-2.5.0-to-2.6.0.mjs +46 -0
- package/pipeline/schemas/prefs.schema.json +277 -67
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/_stack-routing.mjs +79 -0
- package/pipeline/scripts/audit-log-rotate.sh +14 -1
- package/pipeline/scripts/build-skills-index.mjs +11 -0
- package/pipeline/scripts/build-stack-plugins.mjs +35 -60
- package/pipeline/scripts/check-derived-drift.mjs +65 -29
- package/pipeline/scripts/diff-explain.mjs +41 -3
- package/pipeline/scripts/diff-risk-score.mjs +72 -8
- package/pipeline/scripts/gc-worktrees.sh +4 -1
- package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/scripts/learning-curve.mjs +8 -2
- package/pipeline/scripts/match-skills.mjs +8 -2
- package/pipeline/scripts/migrate-prefs.mjs +28 -20
- package/pipeline/scripts/output-quality-check.sh +15 -4
- package/pipeline/scripts/phase-tracker.sh +33 -12
- package/pipeline/scripts/phase0-exit-gate.mjs +3 -2
- package/pipeline/scripts/pre-commit-check.sh +69 -22
- package/pipeline/scripts/render-agent-log-cost.sh +8 -3
- package/pipeline/scripts/render-cost-summary.sh +42 -22
- package/pipeline/scripts/render-work-summary.sh +47 -13
- package/pipeline/scripts/review-scope.mjs +1 -1
- package/pipeline/scripts/run-aggregator.mjs +43 -14
- package/pipeline/scripts/scan-agent-config.sh +1 -1
- package/pipeline/scripts/skill-conformance.mjs +165 -30
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +1 -1
- package/pipeline/scripts/smoke-schema-validation.sh +5 -1
- package/pipeline/scripts/test-gap-rules/android.json +25 -0
- package/pipeline/scripts/test-gap-rules/ios.json +34 -0
- package/pipeline/scripts/test-gap-rules/node.json +29 -0
- package/pipeline/scripts/test-gap-rules/python.json +25 -0
- package/pipeline/scripts/test-gap-scan.mjs +45 -6
- package/pipeline/scripts/uninstall.mjs +196 -14
- package/pipeline/scripts/update-issue-progress.sh +12 -16
- package/pipeline/scripts/validate-complaint-doc.mjs +229 -0
- package/pipeline/scripts/validate-reviewer.mjs +9 -3
- package/pipeline/scripts/worktree-finalize.sh +23 -2
- package/pipeline/skills/.skill-manifest.json +156 -108
- package/pipeline/skills/.skills-index.json +459 -13
- package/pipeline/skills/shared/README.md +15 -11
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-complaint-analysis/SKILL.md +49 -0
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-prune-prompts/SKILL.md +83 -0
- package/pipeline/skills/shared/core/{multi-agent-ship → multi-agent-resume-local}/SKILL.md +10 -6
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +79 -22
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -8
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +1 -1
- package/pipeline/skills/shared/external/ios-coding-standard/modules/_TEMPLATE.yml +2 -2
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +368 -33
- package/pipeline/skills/shared/external/ios-coding-standard/references/swiftlint.draft.yml +1 -2
- package/pipeline/skills/shared/external/ios-coding-standard/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +75 -0
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +131 -0
- package/pipeline/skills/shared/external/ios-module-structure/references/rules.yml +559 -0
- package/pipeline/skills/shared/external/ios-module-structure/scripts/check_structure.py +765 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +187 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +176 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
- package/pipeline/skills/skills-index.md +9 -5
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "android",
|
|
4
|
+
"description": "Android / Jetpack Compose / Kotlin test gap rules. Sources in *.kt; tests under app/src/test/ or app/src/androidTest/.",
|
|
5
|
+
"sourceExtensions": [".kt", ".java"],
|
|
6
|
+
"testPathSuffixes": ["Test.kt", "Tests.kt", "Test.java"],
|
|
7
|
+
"testPathContains": ["/src/test/", "/src/androidTest/", "/test/", "/tests/"],
|
|
8
|
+
"excludePathGlobs": ["build/", ".gradle/", "generated/", "**/BuildConfig.java", "**/R.java"],
|
|
9
|
+
"publicApiPatterns": [
|
|
10
|
+
{ "id": "open_class", "regex": "\\bopen\\s+class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
11
|
+
{ "id": "open_fun", "regex": "\\bopen\\s+fun\\s+([a-z_][A-Za-z0-9_]*)" },
|
|
12
|
+
{ "id": "public_fun", "regex": "^\\s*(?:public\\s+)?fun\\s+([a-z_][A-Za-z0-9_]*)" },
|
|
13
|
+
{ "id": "public_class", "regex": "^\\s*(?:public\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
14
|
+
{ "id": "jvmstatic", "regex": "@JvmStatic\\b" },
|
|
15
|
+
{ "id": "interface", "regex": "^\\s*(?:public\\s+)?interface\\s+([A-Z][A-Za-z0-9_]*)" }
|
|
16
|
+
],
|
|
17
|
+
"composablePatterns": [
|
|
18
|
+
{
|
|
19
|
+
"id": "composable_fun",
|
|
20
|
+
"regex": "@Composable\\s+(?:public\\s+|internal\\s+)?fun\\s+([A-Z][A-Za-z0-9_]*)"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"expectedTestKindsForComposable": ["compose-ui-test"],
|
|
24
|
+
"hint": "Cover @Composable functions with createComposeRule() + onNodeWithText/Tag/ContentDescription assertions; ViewModels with runTest + Turbine."
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "ios",
|
|
4
|
+
"description": "iOS / SwiftUI / UIKit test gap rules. Source files in *.swift; tests in *Tests.swift. Snapshot/ViewInspector tests are also recognised when the source is a SwiftUI View.",
|
|
5
|
+
"sourceExtensions": [".swift"],
|
|
6
|
+
"testPathSuffixes": ["Tests.swift", "Test.swift"],
|
|
7
|
+
"testPathContains": ["/Tests/", "/UnitTests/", "/SnapshotTests/"],
|
|
8
|
+
"excludePathGlobs": [
|
|
9
|
+
"Pods/",
|
|
10
|
+
"Carthage/",
|
|
11
|
+
".build/",
|
|
12
|
+
"DerivedData/",
|
|
13
|
+
"Generated/",
|
|
14
|
+
"Sourcery/",
|
|
15
|
+
"_Generated.swift",
|
|
16
|
+
"Tests/Fixtures/"
|
|
17
|
+
],
|
|
18
|
+
"publicApiPatterns": [
|
|
19
|
+
{ "id": "public_func", "regex": "\\bpublic\\s+func\\s+([A-Za-z_][A-Za-z0-9_]*)" },
|
|
20
|
+
{ "id": "public_class", "regex": "\\bpublic\\s+(?:final\\s+)?class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
21
|
+
{ "id": "public_struct", "regex": "\\bpublic\\s+struct\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
22
|
+
{ "id": "public_enum", "regex": "\\bpublic\\s+enum\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
23
|
+
{ "id": "public_proto", "regex": "\\bpublic\\s+protocol\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
24
|
+
{ "id": "public_init", "regex": "\\bpublic\\s+init\\b" },
|
|
25
|
+
{ "id": "objc_export", "regex": "@objc(?:Members)?\\s+(?:public\\s+)?(?:func|class|var)" }
|
|
26
|
+
],
|
|
27
|
+
"swiftUiViewPatterns": [
|
|
28
|
+
{ "id": "view_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*)\\s*:\\s*View\\b" },
|
|
29
|
+
{ "id": "config_struct", "regex": "struct\\s+([A-Z][A-Za-z0-9_]*Configuration)\\b" }
|
|
30
|
+
],
|
|
31
|
+
"expectedTestKindsForView": ["snapshot", "viewinspector", "unit"],
|
|
32
|
+
"snapshotPathHints": ["SnapshotTests", "__Snapshots__"],
|
|
33
|
+
"hint": "Cover with a 3-layer test (structural ViewInspector + snapshot + unit) per swiftui-qa.md."
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "node",
|
|
4
|
+
"description": "Node.js / TypeScript test gap rules. Sources in *.ts/*.tsx/*.js/*.mjs; tests in *.test.* / *.spec.* / __tests__/.",
|
|
5
|
+
"sourceExtensions": [".ts", ".tsx", ".js", ".mjs", ".cjs"],
|
|
6
|
+
"testPathSuffixes": [
|
|
7
|
+
".test.ts",
|
|
8
|
+
".test.tsx",
|
|
9
|
+
".test.js",
|
|
10
|
+
".test.mjs",
|
|
11
|
+
".spec.ts",
|
|
12
|
+
".spec.tsx",
|
|
13
|
+
".spec.js",
|
|
14
|
+
".spec.mjs"
|
|
15
|
+
],
|
|
16
|
+
"testPathContains": ["/__tests__/", "/test/", "/tests/"],
|
|
17
|
+
"excludePathGlobs": ["node_modules/", "dist/", "build/", ".next/", "coverage/", "*.d.ts"],
|
|
18
|
+
"publicApiPatterns": [
|
|
19
|
+
{
|
|
20
|
+
"id": "named_export",
|
|
21
|
+
"regex": "^export\\s+(?:async\\s+)?(?:function|class|const|let|interface|type|enum)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "default_export",
|
|
25
|
+
"regex": "^export\\s+default\\s+(?:async\\s+)?(?:function|class)\\s+([A-Za-z_$][A-Za-z0-9_$]*)"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"hint": "Cover named exports with vitest/jest test('<name>', ...). React components: React Testing Library (render + screen + userEvent)."
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"stack": "python",
|
|
4
|
+
"description": "Python test gap rules. Sources in *.py; tests in test_*.py / *_test.py / tests/.",
|
|
5
|
+
"sourceExtensions": [".py"],
|
|
6
|
+
"testPathSuffixes": ["_test.py"],
|
|
7
|
+
"testPathPrefixes": ["test_"],
|
|
8
|
+
"testPathContains": ["/tests/", "/test/"],
|
|
9
|
+
"excludePathGlobs": [
|
|
10
|
+
"__pycache__/",
|
|
11
|
+
".venv/",
|
|
12
|
+
"venv/",
|
|
13
|
+
".tox/",
|
|
14
|
+
"build/",
|
|
15
|
+
"dist/",
|
|
16
|
+
"*.egg-info/",
|
|
17
|
+
"**/migrations/**"
|
|
18
|
+
],
|
|
19
|
+
"publicApiPatterns": [
|
|
20
|
+
{ "id": "top_def", "regex": "^def\\s+([a-z_][a-z0-9_]*)" },
|
|
21
|
+
{ "id": "top_class", "regex": "^class\\s+([A-Z][A-Za-z0-9_]*)" },
|
|
22
|
+
{ "id": "async_def", "regex": "^async\\s+def\\s+([a-z_][a-z0-9_]*)" }
|
|
23
|
+
],
|
|
24
|
+
"hint": "Cover with pytest test_<symbol> style. Async with pytest-asyncio. Avoid name collision with private helpers (prefix _)."
|
|
25
|
+
}
|
|
@@ -141,6 +141,45 @@ function isExcluded(path) {
|
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
// git quotes a `diff --git` path (core.quotePath, on by default) whenever it
|
|
145
|
+
// has a non-ASCII byte or other unusual character - e.g. a Turkish filename
|
|
146
|
+
// renders as `diff --git "a/\303\226deme.swift" "b/\303\226deme.swift"`, not
|
|
147
|
+
// the bare a/...b/... form. The plain regex this replaced never matched that
|
|
148
|
+
// line at all, so every non-ASCII-named file's added lines were silently
|
|
149
|
+
// dropped from the test-gap scan.
|
|
150
|
+
const DIFF_GIT_QUOTED_RE = /^diff --git "a\/((?:[^"\\]|\\.)*)" "b\/((?:[^"\\]|\\.)*)"$/;
|
|
151
|
+
const DIFF_GIT_UNQUOTED_RE = /^diff --git a\/(.+?) b\/(.+)$/;
|
|
152
|
+
|
|
153
|
+
function unquoteGitPath(s) {
|
|
154
|
+
const bytes = [];
|
|
155
|
+
for (let i = 0; i < s.length; i++) {
|
|
156
|
+
if (s[i] === "\\") {
|
|
157
|
+
const octal = s.slice(i + 1, i + 4);
|
|
158
|
+
if (/^[0-7]{3}$/.test(octal)) {
|
|
159
|
+
bytes.push(parseInt(octal, 8));
|
|
160
|
+
i += 3;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const simple = { "\\": 92, '"': 34, t: 9, n: 10, r: 13 };
|
|
164
|
+
const next = s[i + 1];
|
|
165
|
+
if (next in simple) {
|
|
166
|
+
bytes.push(simple[next]);
|
|
167
|
+
i += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
bytes.push(s.charCodeAt(i));
|
|
172
|
+
}
|
|
173
|
+
return Buffer.from(bytes).toString("utf-8");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function matchDiffGitHeader(line) {
|
|
177
|
+
const q = line.match(DIFF_GIT_QUOTED_RE);
|
|
178
|
+
if (q) return unquoteGitPath(q[2]);
|
|
179
|
+
const u = line.match(DIFF_GIT_UNQUOTED_RE);
|
|
180
|
+
return u ? u[2] : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
144
183
|
function isTestPath(path) {
|
|
145
184
|
if (Array.isArray(rules.testPathSuffixes) && rules.testPathSuffixes.some((s) => path.endsWith(s)))
|
|
146
185
|
return true;
|
|
@@ -190,9 +229,9 @@ function parseDiffAddedLines(diffText) {
|
|
|
190
229
|
const perFile = new Map();
|
|
191
230
|
let cur = null;
|
|
192
231
|
for (const line of diffText.split("\n")) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
cur =
|
|
232
|
+
const p = matchDiffGitHeader(line);
|
|
233
|
+
if (p !== null) {
|
|
234
|
+
cur = p;
|
|
196
235
|
if (!perFile.has(cur)) perFile.set(cur, { lines: [], isNew: false });
|
|
197
236
|
continue;
|
|
198
237
|
}
|
|
@@ -300,9 +339,9 @@ function symbolMentionedInTest(diffText, symbol) {
|
|
|
300
339
|
const lines = diffText.split("\n");
|
|
301
340
|
let inTestFile = false;
|
|
302
341
|
for (const line of lines) {
|
|
303
|
-
const fm = line
|
|
304
|
-
if (fm) {
|
|
305
|
-
inTestFile = isTestPath(fm
|
|
342
|
+
const fm = matchDiffGitHeader(line);
|
|
343
|
+
if (fm !== null) {
|
|
344
|
+
inTestFile = isTestPath(fm);
|
|
306
345
|
continue;
|
|
307
346
|
}
|
|
308
347
|
if (!inTestFile) continue;
|
|
@@ -35,9 +35,20 @@
|
|
|
35
35
|
* @module pipeline/scripts/runtime/uninstall
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
chmodSync,
|
|
40
|
+
existsSync,
|
|
41
|
+
lstatSync,
|
|
42
|
+
readdirSync,
|
|
43
|
+
readFileSync,
|
|
44
|
+
realpathSync,
|
|
45
|
+
renameSync,
|
|
46
|
+
rmSync,
|
|
47
|
+
statSync,
|
|
48
|
+
writeFileSync,
|
|
49
|
+
} from "fs";
|
|
39
50
|
import { execFileSync } from "child_process";
|
|
40
|
-
import { join } from "path";
|
|
51
|
+
import { join, dirname } from "path";
|
|
41
52
|
import { homedir } from "os";
|
|
42
53
|
import { pathToFileURL } from "url";
|
|
43
54
|
import { createInterface } from "readline";
|
|
@@ -100,6 +111,43 @@ const adapterTarget = (() => {
|
|
|
100
111
|
return raw ? raw.slice("--target=".length) : process.cwd();
|
|
101
112
|
})();
|
|
102
113
|
|
|
114
|
+
/**
|
|
115
|
+
* tmp + rename, preserving a symlinked target and the existing file mode.
|
|
116
|
+
*
|
|
117
|
+
* Duplicated from install/_common.mjs rather than imported: this script runs
|
|
118
|
+
* standalone from ~/.claude/scripts, where the install tree does not exist.
|
|
119
|
+
* A plain writeFileSync could leave the host's settings.json truncated on a
|
|
120
|
+
* crash; a naive rename would replace a dotfiles symlink with a regular file
|
|
121
|
+
* and reset a 0600 file to 0644.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} path
|
|
124
|
+
* @param {string} content
|
|
125
|
+
*/
|
|
126
|
+
function atomicWrite(path, content) {
|
|
127
|
+
let target = path;
|
|
128
|
+
try {
|
|
129
|
+
if (lstatSync(path).isSymbolicLink()) target = realpathSync(path);
|
|
130
|
+
} catch {
|
|
131
|
+
// Missing file: nothing to preserve.
|
|
132
|
+
}
|
|
133
|
+
let mode;
|
|
134
|
+
try {
|
|
135
|
+
mode = statSync(target).mode & 0o777;
|
|
136
|
+
} catch {
|
|
137
|
+
mode = undefined;
|
|
138
|
+
}
|
|
139
|
+
const tmp = `${target}.tmp-${process.pid}`;
|
|
140
|
+
writeFileSync(tmp, content);
|
|
141
|
+
if (mode !== undefined) {
|
|
142
|
+
try {
|
|
143
|
+
chmodSync(tmp, mode);
|
|
144
|
+
} catch {
|
|
145
|
+
// Best effort: a failed chmod must not lose the write.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
renameSync(tmp, target);
|
|
149
|
+
}
|
|
150
|
+
|
|
103
151
|
/**
|
|
104
152
|
* Report a planned action. Always logged; in --dry-run mode, no side effects
|
|
105
153
|
* are performed.
|
|
@@ -197,10 +245,7 @@ function deregisterCodexMcpServer() {
|
|
|
197
245
|
*/
|
|
198
246
|
function deregisterClaudeMcpServer() {
|
|
199
247
|
const localBin = join(homedir(), ".local", "bin", "claude");
|
|
200
|
-
deregisterMcpServer("claude", existsSync(localBin) ? localBin : undefined, [
|
|
201
|
-
"--scope",
|
|
202
|
-
"user",
|
|
203
|
-
]);
|
|
248
|
+
deregisterMcpServer("claude", existsSync(localBin) ? localBin : undefined, ["--scope", "user"]);
|
|
204
249
|
}
|
|
205
250
|
|
|
206
251
|
/**
|
|
@@ -220,6 +265,104 @@ function deregisterCopilotMcpServer() {
|
|
|
220
265
|
* @param {string} parent
|
|
221
266
|
* @param {(name: string) => boolean} predicate
|
|
222
267
|
*/
|
|
268
|
+
/**
|
|
269
|
+
* Remove the pipeline's command namespace while preserving user-authored
|
|
270
|
+
* local-only alias wrappers (frontmatter `local-only: true`). Those wrappers
|
|
271
|
+
* exist ONLY under the host dir - install/claude.mjs snapshots them across its
|
|
272
|
+
* wipe for the same reason - so removing the namespace wholesale would destroy
|
|
273
|
+
* content a reinstall can never bring back.
|
|
274
|
+
* @param {string} cmdDir - e.g. ~/.claude/commands/multi-agent
|
|
275
|
+
* @returns {number} preserved wrapper dirs
|
|
276
|
+
*/
|
|
277
|
+
function rmCommandsPreservingLocalOnly(cmdDir) {
|
|
278
|
+
// An install interrupted between its wipe and its restore leaves wrappers in
|
|
279
|
+
// a hidden stash beside the namespace. Adopt them back before deciding what
|
|
280
|
+
// to preserve, or the "local-only wrappers preserved" promise below strands
|
|
281
|
+
// them in a dot-directory the user never sees.
|
|
282
|
+
const stashDir = join(dirname(cmdDir), ".multi-agent-wrapper-stash");
|
|
283
|
+
if (existsSync(stashDir) && !dryRun) {
|
|
284
|
+
for (const name of readdirSync(stashDir)) {
|
|
285
|
+
const to = join(cmdDir, name);
|
|
286
|
+
if (existsSync(to)) continue;
|
|
287
|
+
try {
|
|
288
|
+
renameSync(join(stashDir, name), to);
|
|
289
|
+
console.log(` recovered stashed wrapper from an interrupted install: ${name}`);
|
|
290
|
+
} catch {
|
|
291
|
+
/* best effort - a failed recovery must not block the uninstall */
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
rmIfExists(stashDir);
|
|
295
|
+
}
|
|
296
|
+
if (!existsSync(cmdDir)) return 0;
|
|
297
|
+
const isLocalOnly = (dir) => {
|
|
298
|
+
const skill = join(dir, "SKILL.md");
|
|
299
|
+
try {
|
|
300
|
+
return existsSync(skill) && /^local-only:\s*true\s*$/m.test(readFileSync(skill, "utf-8"));
|
|
301
|
+
} catch {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const preserved = readdirSync(cmdDir, { withFileTypes: true })
|
|
306
|
+
.filter((e) => e.isDirectory() && isLocalOnly(join(cmdDir, e.name)))
|
|
307
|
+
.map((e) => e.name);
|
|
308
|
+
if (preserved.length === 0) {
|
|
309
|
+
rmIfExists(cmdDir);
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
for (const entry of readdirSync(cmdDir)) {
|
|
313
|
+
if (preserved.includes(entry)) continue;
|
|
314
|
+
rmIfExists(join(cmdDir, entry));
|
|
315
|
+
}
|
|
316
|
+
console.log(
|
|
317
|
+
` preserved ${preserved.length} local-only alias wrapper(s): ${preserved.join(", ")}`,
|
|
318
|
+
);
|
|
319
|
+
return preserved.length;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Written by install/_platform-filter.mjs beside the skills it delivered. */
|
|
323
|
+
const EXTERNAL_SKILLS_MANIFEST = ".external-skills-manifest.json";
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Remove the external skill catalog THIS install delivered, read from the
|
|
327
|
+
* manifest the installer wrote beside it.
|
|
328
|
+
*
|
|
329
|
+
* Deliberately not derived from `.skills-index.json`: that file is the shipped
|
|
330
|
+
* catalog copied verbatim, so it lists platform-filtered skills that were never
|
|
331
|
+
* installed, and it says nothing about which package version's tree is on disk.
|
|
332
|
+
* Either gap would delete a user-authored skill dir that happens to share a
|
|
333
|
+
* catalog name. Same contract as `.plugin-skills-manifest.json`.
|
|
334
|
+
*
|
|
335
|
+
* No manifest (a pre-manifest install) means ours and theirs are
|
|
336
|
+
* indistinguishable, so nothing is removed and the user is told why.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} skillsDir
|
|
339
|
+
* @returns {number} dirs removed
|
|
340
|
+
*/
|
|
341
|
+
function rmExternalDeliveredSkills(skillsDir) {
|
|
342
|
+
if (!existsSync(skillsDir)) return 0;
|
|
343
|
+
const manifestPath = join(skillsDir, EXTERNAL_SKILLS_MANIFEST);
|
|
344
|
+
let names = null;
|
|
345
|
+
if (existsSync(manifestPath)) {
|
|
346
|
+
try {
|
|
347
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
348
|
+
if (Array.isArray(parsed)) names = new Set(parsed);
|
|
349
|
+
} catch {
|
|
350
|
+
names = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (!names) {
|
|
354
|
+
console.log(
|
|
355
|
+
` note: no ${EXTERNAL_SKILLS_MANIFEST} under ${skillsDir} - external skill dirs left in ` +
|
|
356
|
+
`place (cannot tell pipeline-delivered from user-authored; re-run install once, then uninstall)`,
|
|
357
|
+
);
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
let n = rmMatchingDirs(skillsDir, (name) => names.has(name));
|
|
361
|
+
n += rmMatchingFiles(skillsDir, (name) => /^NOTICE-.*\.md$/.test(name));
|
|
362
|
+
rmIfExists(manifestPath);
|
|
363
|
+
return n;
|
|
364
|
+
}
|
|
365
|
+
|
|
223
366
|
function rmMatchingDirs(parent, predicate) {
|
|
224
367
|
if (!existsSync(parent)) return 0;
|
|
225
368
|
let count = 0;
|
|
@@ -248,6 +391,31 @@ function rmMatchingFiles(parent, predicate) {
|
|
|
248
391
|
return count;
|
|
249
392
|
}
|
|
250
393
|
|
|
394
|
+
// Copilot CLI / Codex CLI have no plugin loader, so install/_plugin-skills.mjs
|
|
395
|
+
// copies each stack plugin's authored skills straight into the flat skills/
|
|
396
|
+
// dir (~100 dirs across a typical set of enabled plugins). None of those
|
|
397
|
+
// names are in PIPELINE_CORE_SKILL_DIRS - the allowlist above only covers the
|
|
398
|
+
// pipeline's OWN skills - so they were never removed on uninstall. The
|
|
399
|
+
// installer now writes PLUGIN_SKILLS_MANIFEST listing exactly what it
|
|
400
|
+
// delivered; read it back here rather than guessing plugin skill names.
|
|
401
|
+
const PLUGIN_SKILLS_MANIFEST = ".plugin-skills-manifest.json";
|
|
402
|
+
|
|
403
|
+
function rmPluginDeliveredSkills(skillsDir) {
|
|
404
|
+
const manifestPath = join(skillsDir, PLUGIN_SKILLS_MANIFEST);
|
|
405
|
+
if (!existsSync(manifestPath)) return 0;
|
|
406
|
+
let names;
|
|
407
|
+
try {
|
|
408
|
+
names = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
409
|
+
} catch {
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
if (!Array.isArray(names)) return 0;
|
|
413
|
+
const nameSet = new Set(names);
|
|
414
|
+
const count = rmMatchingDirs(skillsDir, (name) => nameSet.has(name));
|
|
415
|
+
rmIfExists(manifestPath);
|
|
416
|
+
return count;
|
|
417
|
+
}
|
|
418
|
+
|
|
251
419
|
// Explicit terminator install/copilot.mjs writes after the pipeline section
|
|
252
420
|
// (v11.4.1+). Must stay in sync with INSTRUCTIONS_END_MARKER there.
|
|
253
421
|
const COPILOT_END_MARKER = "<!-- multi-agent-pipeline:copilot-instructions:end -->";
|
|
@@ -339,7 +507,7 @@ function stripManagedBlock(filePath) {
|
|
|
339
507
|
rmSync(filePath, { force: true });
|
|
340
508
|
console.log(` removed (was pipeline-only): ${filePath}`);
|
|
341
509
|
} else {
|
|
342
|
-
|
|
510
|
+
atomicWrite(filePath, remaining + "\n");
|
|
343
511
|
console.log(` cleaned pipeline section: ${filePath}`);
|
|
344
512
|
}
|
|
345
513
|
return true;
|
|
@@ -356,7 +524,7 @@ function stripManagedBlock(filePath) {
|
|
|
356
524
|
rmSync(filePath, { force: true });
|
|
357
525
|
console.log(` removed (was pipeline-only): ${filePath}`);
|
|
358
526
|
} else {
|
|
359
|
-
|
|
527
|
+
atomicWrite(filePath, cleaned + "\n");
|
|
360
528
|
console.log(` cleaned pipeline block from: ${filePath}`);
|
|
361
529
|
}
|
|
362
530
|
return true;
|
|
@@ -387,13 +555,21 @@ function cleanClaudeSettings(settingsPath) {
|
|
|
387
555
|
if (settings.hooks?.PreToolUse?.length) {
|
|
388
556
|
const before = settings.hooks.PreToolUse.length;
|
|
389
557
|
// Match both the current matcher ("Bash") and the dead pre-v11.4.1 one
|
|
390
|
-
// ("Bash(git commit:*)"); the
|
|
391
|
-
//
|
|
558
|
+
// ("Bash(git commit:*)"); the command identifies the entry as ours either
|
|
559
|
+
// way. install/claude.mjs registers TWO PreToolUse gates this way -
|
|
560
|
+
// pre-commit-check.sh AND agent-guard.sh - and only the first was ever
|
|
561
|
+
// stripped here. After ~/.claude/scripts/ is deleted below, the
|
|
562
|
+
// agent-guard.sh entry pointed at a script that no longer existed, so
|
|
563
|
+
// every subsequent Bash call fired a hook error.
|
|
392
564
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
393
565
|
(h) =>
|
|
394
566
|
!(
|
|
395
567
|
(h.matcher === "Bash" || h.matcher === "Bash(git commit:*)") &&
|
|
396
|
-
h.hooks?.some(
|
|
568
|
+
h.hooks?.some(
|
|
569
|
+
(sub) =>
|
|
570
|
+
sub.command?.includes("pre-commit-check.sh") ||
|
|
571
|
+
sub.command?.includes("agent-guard.sh"),
|
|
572
|
+
)
|
|
397
573
|
),
|
|
398
574
|
);
|
|
399
575
|
if (settings.hooks.PreToolUse.length < before) touched = true;
|
|
@@ -405,7 +581,7 @@ function cleanClaudeSettings(settingsPath) {
|
|
|
405
581
|
if (dryRun) {
|
|
406
582
|
report("would update", settingsPath);
|
|
407
583
|
} else {
|
|
408
|
-
|
|
584
|
+
atomicWrite(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
409
585
|
console.log(` cleaned pipeline hook + env from: ${settingsPath}`);
|
|
410
586
|
}
|
|
411
587
|
}
|
|
@@ -465,6 +641,7 @@ export async function main() {
|
|
|
465
641
|
);
|
|
466
642
|
console.log(" - ~/.claude/CLAUDE.md (your customizations)");
|
|
467
643
|
console.log(" - ~/.claude/rules/ (user-owned; installed write-if-missing, never overwritten)");
|
|
644
|
+
console.log(" - Local-only alias wrappers under commands/multi-agent/ (user-authored)");
|
|
468
645
|
if (!allData) console.log(" - ~/.claude/multi-agent-preferences.json (your settings)");
|
|
469
646
|
console.log(" - Your own content in copilot-instructions.md above the pipeline section");
|
|
470
647
|
console.log("");
|
|
@@ -479,7 +656,7 @@ export async function main() {
|
|
|
479
656
|
console.log(" [Claude Code] Removing...");
|
|
480
657
|
deregisterClaudeMcpServer();
|
|
481
658
|
const CLAUDE = join(HOME, ".claude");
|
|
482
|
-
|
|
659
|
+
rmCommandsPreservingLocalOnly(join(CLAUDE, "commands", "multi-agent"));
|
|
483
660
|
rmIfExists(join(CLAUDE, "scripts"));
|
|
484
661
|
// Pipeline-managed trees the installer lays down alongside scripts/.
|
|
485
662
|
rmIfExists(join(CLAUDE, "multi-agent-refs"));
|
|
@@ -506,6 +683,7 @@ export async function main() {
|
|
|
506
683
|
name === "figma-to-component",
|
|
507
684
|
);
|
|
508
685
|
n += rmMatchingDirs(skills, (name) => PIPELINE_CORE_SKILL_DIRS.includes(name));
|
|
686
|
+
n += rmExternalDeliveredSkills(skills);
|
|
509
687
|
if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
|
|
510
688
|
rmIfExists(join(skills, ".skills-index.json"));
|
|
511
689
|
rmIfExists(join(skills, "skills-index.md"));
|
|
@@ -540,10 +718,13 @@ export async function main() {
|
|
|
540
718
|
name === "figma-to-component",
|
|
541
719
|
);
|
|
542
720
|
n += rmMatchingDirs(skills, (name) => PIPELINE_CORE_SKILL_DIRS.includes(name));
|
|
721
|
+
n += rmPluginDeliveredSkills(skills);
|
|
722
|
+
n += rmExternalDeliveredSkills(skills);
|
|
543
723
|
if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
|
|
544
724
|
rmIfExists(join(skills, ".skills-index.json"));
|
|
545
725
|
rmIfExists(join(skills, "skills-index.md"));
|
|
546
726
|
rmIfExists(join(skills, "README.md"));
|
|
727
|
+
rmIfExists(join(COP, "rules"));
|
|
547
728
|
// Agent definitions are FILES; remove only the pipeline-shipped names so
|
|
548
729
|
// user-authored agents survive.
|
|
549
730
|
rmMatchingFiles(join(COP, "agents"), (name) => PIPELINE_AGENT_FILES.includes(name));
|
|
@@ -613,10 +794,11 @@ export async function main() {
|
|
|
613
794
|
// Codex's own `.system` set plus user skills; `prompts/` and `agents/` hold
|
|
614
795
|
// user-authored files.
|
|
615
796
|
const skills = join(CODEX, "skills");
|
|
616
|
-
|
|
797
|
+
let n = rmMatchingDirs(
|
|
617
798
|
skills,
|
|
618
799
|
(name) => name === "multi-agent" || name.startsWith("multi-agent-"),
|
|
619
800
|
);
|
|
801
|
+
n += rmPluginDeliveredSkills(skills);
|
|
620
802
|
if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
|
|
621
803
|
rmIfExists(join(CODEX, "prompts", "multi-agent.md"));
|
|
622
804
|
removeGeneratedCodexAgents(join(CODEX, "agents"));
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
# update-issue-progress.sh <task-id>
|
|
15
15
|
#
|
|
16
16
|
# Inputs:
|
|
17
|
-
# $HOME/.claude/
|
|
18
|
-
# .
|
|
17
|
+
# $HOME/.claude/logs/multi-agent/{project}/{taskId}/agent-state.json
|
|
18
|
+
# .githubIssue.owner, .githubIssue.repo, .githubIssue.number
|
|
19
19
|
# .flags.implementation, .flags.testing, .flags.codeConnect, .flags.wiki
|
|
20
20
|
#
|
|
21
21
|
# Side effects:
|
|
@@ -86,11 +86,12 @@ if [ "$TASK_ID" = "--rewrite" ]; then
|
|
|
86
86
|
exit 0
|
|
87
87
|
fi
|
|
88
88
|
|
|
89
|
-
# Resolve
|
|
90
|
-
|
|
89
|
+
# Resolve agent-state.json. multi-repo-pipeline.sh writes it under a
|
|
90
|
+
# per-project directory ($HOME/.claude/logs/multi-agent/{project}/{taskId}/)
|
|
91
|
+
# - not the ~/.claude/projects/*/state/ path this used to glob, which no
|
|
92
|
+
# writer in the pipeline ever populates.
|
|
91
93
|
AGENT_STATE=""
|
|
92
|
-
for
|
|
93
|
-
candidate="$dir/state/$TASK_ID/agent-state.json"
|
|
94
|
+
for candidate in "$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/agent-state.json; do
|
|
94
95
|
if [ -f "$candidate" ]; then
|
|
95
96
|
AGENT_STATE="$candidate"
|
|
96
97
|
break
|
|
@@ -102,18 +103,13 @@ if [ -z "$AGENT_STATE" ]; then
|
|
|
102
103
|
exit 2
|
|
103
104
|
fi
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if [ -z "$ORG_REPO" ] || ! [[ "$ORG_REPO" == */* ]]; then
|
|
110
|
-
if [[ "$ISSUE_URL" =~ github\.com/([^/]+/[^/]+)/issues/ ]]; then
|
|
111
|
-
ORG_REPO="${BASH_REMATCH[1]}"
|
|
112
|
-
fi
|
|
113
|
-
fi
|
|
106
|
+
# agent-state.schema.json has no `.issue`/`.repos` properties (additionalProperties
|
|
107
|
+
# is false) - the real GitHub-issue coordinates live at `.githubIssue.{owner,repo,number}`.
|
|
108
|
+
ISSUE_NUM=$(jq -r '.githubIssue.number // empty' "$AGENT_STATE")
|
|
109
|
+
ORG_REPO=$(jq -r 'if .githubIssue then (.githubIssue.owner + "/" + .githubIssue.repo) else empty end' "$AGENT_STATE")
|
|
114
110
|
|
|
115
111
|
if [ -z "$ORG_REPO" ] || [ -z "$ISSUE_NUM" ]; then
|
|
116
|
-
echo "update-issue-progress:
|
|
112
|
+
echo "update-issue-progress: no githubIssue in state for task=$TASK_ID (Jira-originated task, or issue not yet linked)" >&2
|
|
117
113
|
exit 3
|
|
118
114
|
fi
|
|
119
115
|
|