forge-workflow 0.0.4 → 0.0.6

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.
Files changed (248) hide show
  1. package/.claude/commands/dev.md +345 -340
  2. package/.claude/commands/plan.md +566 -521
  3. package/.claude/commands/premerge.md +186 -176
  4. package/.claude/commands/research.md +42 -42
  5. package/.claude/commands/review.md +448 -442
  6. package/.claude/commands/rollback.md +721 -721
  7. package/.claude/commands/ship.md +212 -164
  8. package/.claude/commands/sonarcloud.md +152 -152
  9. package/.claude/commands/status.md +90 -48
  10. package/.claude/commands/validate.md +288 -282
  11. package/.claude/commands/verify.md +269 -221
  12. package/.claude/rules/greptile-review-process.md +285 -285
  13. package/.claude/rules/workflow.md +121 -105
  14. package/.claude/scripts/greptile-resolve.sh +558 -526
  15. package/.claude/scripts/load-env.sh +32 -32
  16. package/.cline/workflows/dev.md +342 -337
  17. package/.cline/workflows/plan.md +563 -518
  18. package/.cline/workflows/premerge.md +183 -173
  19. package/.cline/workflows/research.md +39 -39
  20. package/.cline/workflows/review.md +445 -439
  21. package/.cline/workflows/rollback.md +718 -718
  22. package/.cline/workflows/ship.md +209 -161
  23. package/.cline/workflows/sonarcloud.md +146 -146
  24. package/.cline/workflows/status.md +87 -45
  25. package/.cline/workflows/validate.md +285 -279
  26. package/.cline/workflows/verify.md +266 -218
  27. package/.codex/config.toml +11 -11
  28. package/.codex/skills/dev/SKILL.md +345 -340
  29. package/.codex/skills/plan/SKILL.md +566 -521
  30. package/.codex/skills/premerge/SKILL.md +186 -176
  31. package/.codex/skills/research/SKILL.md +42 -42
  32. package/.codex/skills/review/SKILL.md +448 -442
  33. package/.codex/skills/rollback/SKILL.md +721 -721
  34. package/.codex/skills/ship/SKILL.md +212 -164
  35. package/.codex/skills/sonarcloud/SKILL.md +149 -149
  36. package/.codex/skills/status/SKILL.md +90 -48
  37. package/.codex/skills/validate/SKILL.md +288 -282
  38. package/.codex/skills/verify/SKILL.md +269 -221
  39. package/.cursor/commands/dev.md +342 -337
  40. package/.cursor/commands/plan.md +563 -518
  41. package/.cursor/commands/premerge.md +183 -173
  42. package/.cursor/commands/research.md +39 -39
  43. package/.cursor/commands/review.md +445 -439
  44. package/.cursor/commands/rollback.md +718 -718
  45. package/.cursor/commands/ship.md +209 -161
  46. package/.cursor/commands/sonarcloud.md +146 -146
  47. package/.cursor/commands/status.md +87 -45
  48. package/.cursor/commands/validate.md +285 -279
  49. package/.cursor/commands/verify.md +266 -218
  50. package/.cursor/rules/permissions-guidance.mdc +37 -37
  51. package/.forge/hooks/check-tdd.js +240 -240
  52. package/.github/PLUGIN_TEMPLATE.json +32 -32
  53. package/.github/prompts/dev.prompt.md +347 -342
  54. package/.github/prompts/plan.prompt.md +568 -523
  55. package/.github/prompts/premerge.prompt.md +188 -178
  56. package/.github/prompts/research.prompt.md +44 -44
  57. package/.github/prompts/review.prompt.md +450 -444
  58. package/.github/prompts/rollback.prompt.md +723 -723
  59. package/.github/prompts/ship.prompt.md +214 -166
  60. package/.github/prompts/sonarcloud.prompt.md +151 -151
  61. package/.github/prompts/status.prompt.md +92 -50
  62. package/.github/prompts/validate.prompt.md +290 -284
  63. package/.github/prompts/verify.prompt.md +271 -223
  64. package/.github/workflows/beads-to-github.yml +56 -0
  65. package/.github/workflows/github-to-beads.yml +97 -0
  66. package/.kilocode/workflows/dev.md +346 -341
  67. package/.kilocode/workflows/plan.md +567 -522
  68. package/.kilocode/workflows/premerge.md +187 -177
  69. package/.kilocode/workflows/research.md +43 -43
  70. package/.kilocode/workflows/review.md +449 -443
  71. package/.kilocode/workflows/rollback.md +722 -722
  72. package/.kilocode/workflows/ship.md +213 -165
  73. package/.kilocode/workflows/sonarcloud.md +150 -150
  74. package/.kilocode/workflows/status.md +91 -49
  75. package/.kilocode/workflows/validate.md +289 -283
  76. package/.kilocode/workflows/verify.md +270 -222
  77. package/.mcp.json.example +12 -12
  78. package/.opencode/commands/dev.md +345 -340
  79. package/.opencode/commands/plan.md +566 -521
  80. package/.opencode/commands/premerge.md +186 -176
  81. package/.opencode/commands/research.md +42 -42
  82. package/.opencode/commands/review.md +448 -442
  83. package/.opencode/commands/rollback.md +721 -721
  84. package/.opencode/commands/ship.md +212 -164
  85. package/.opencode/commands/sonarcloud.md +149 -149
  86. package/.opencode/commands/status.md +90 -48
  87. package/.opencode/commands/validate.md +288 -282
  88. package/.opencode/commands/verify.md +269 -221
  89. package/.roo/commands/dev.md +346 -341
  90. package/.roo/commands/plan.md +567 -522
  91. package/.roo/commands/premerge.md +187 -177
  92. package/.roo/commands/research.md +43 -43
  93. package/.roo/commands/review.md +449 -443
  94. package/.roo/commands/rollback.md +722 -722
  95. package/.roo/commands/ship.md +213 -165
  96. package/.roo/commands/sonarcloud.md +150 -150
  97. package/.roo/commands/status.md +91 -49
  98. package/.roo/commands/validate.md +289 -283
  99. package/.roo/commands/verify.md +270 -222
  100. package/AGENTS.md +272 -175
  101. package/CLAUDE.md +110 -100
  102. package/README.md +429 -416
  103. package/bin/forge-cmd.js +317 -313
  104. package/bin/forge-preflight.js +322 -309
  105. package/bin/forge.js +4765 -4303
  106. package/docs/AGENT_INSTALL_PROMPT.md +342 -342
  107. package/docs/BEADS_GITHUB_SYNC.md +251 -251
  108. package/docs/ENHANCED_ONBOARDING.md +612 -602
  109. package/docs/EXAMPLES.md +482 -482
  110. package/docs/GREPTILE_SETUP.md +400 -400
  111. package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
  112. package/docs/ROADMAP.md +359 -359
  113. package/docs/SETUP.md +663 -631
  114. package/docs/TOOLCHAIN.md +653 -630
  115. package/docs/VALIDATION.md +363 -363
  116. package/install.sh +40 -1056
  117. package/lefthook.yml +50 -39
  118. package/lib/agents/README.md +198 -198
  119. package/lib/agents/claude.plugin.json +28 -28
  120. package/lib/agents/cline.plugin.json +22 -22
  121. package/lib/agents/codex.plugin.json +19 -19
  122. package/lib/agents/copilot.plugin.json +24 -24
  123. package/lib/agents/cursor.plugin.json +25 -25
  124. package/lib/agents/kilocode.plugin.json +22 -22
  125. package/lib/agents/opencode.plugin.json +20 -20
  126. package/lib/agents/roo.plugin.json +23 -23
  127. package/lib/agents-config.js +2112 -2112
  128. package/lib/beads-health-check.js +143 -0
  129. package/lib/beads-setup.js +341 -0
  130. package/lib/beads-sync-scaffold.js +260 -0
  131. package/lib/commands/_registry.js +134 -0
  132. package/lib/commands/clean.js +181 -0
  133. package/lib/commands/dev.js +571 -513
  134. package/lib/commands/plan.js +692 -692
  135. package/lib/commands/push.js +196 -0
  136. package/lib/commands/recommend.js +119 -119
  137. package/lib/commands/ship.js +377 -377
  138. package/lib/commands/status.js +378 -378
  139. package/lib/commands/sync.js +55 -0
  140. package/lib/commands/team.js +37 -0
  141. package/lib/commands/test.js +207 -0
  142. package/lib/commands/validate.js +602 -602
  143. package/lib/commands/worktree.js +310 -0
  144. package/lib/context-merge.js +359 -359
  145. package/lib/dep-guard/analyzer.js +294 -294
  146. package/lib/dep-guard/behavior-detector.js +98 -98
  147. package/lib/dep-guard/contract-detector.js +162 -162
  148. package/lib/dep-guard/import-detector.js +498 -498
  149. package/lib/dep-guard/path-utils.js +13 -13
  150. package/lib/dep-guard/rubric.js +120 -120
  151. package/lib/dep-guard/task-parser.js +318 -318
  152. package/lib/detect-agent.js +191 -191
  153. package/lib/detect-worktree.js +47 -47
  154. package/lib/docs-command.js +51 -0
  155. package/lib/docs-copy.js +50 -0
  156. package/lib/file-hash.js +26 -26
  157. package/lib/freshness-token.js +148 -0
  158. package/lib/greptile-match.js +80 -0
  159. package/lib/husky-migration.js +450 -0
  160. package/lib/lefthook-check.js +65 -0
  161. package/lib/pat-setup.js +207 -0
  162. package/lib/plugin-catalog.js +350 -350
  163. package/lib/plugin-manager.js +166 -166
  164. package/lib/plugin-recommender.js +141 -141
  165. package/lib/project-discovery.js +491 -491
  166. package/lib/reset.js +309 -0
  167. package/lib/setup-action-log.js +139 -139
  168. package/lib/setup-summary-renderer.js +106 -106
  169. package/lib/setup-utils.js +96 -0
  170. package/lib/setup.js +192 -192
  171. package/lib/smart-merge.js +64 -0
  172. package/lib/symlink-utils.js +81 -0
  173. package/lib/task-ownership.js +117 -0
  174. package/lib/workflow-profiles.js +197 -197
  175. package/package.json +131 -128
  176. package/scripts/beads-context.sh +426 -0
  177. package/scripts/beads-context.test.js +567 -0
  178. package/scripts/behavioral-judge.sh +378 -0
  179. package/scripts/benchmark.js +85 -0
  180. package/scripts/branch-protection.js +183 -0
  181. package/scripts/check-agents.js +172 -0
  182. package/scripts/check-forge-token.js +98 -0
  183. package/scripts/commitlint.js +42 -0
  184. package/scripts/conflict-detect.sh +323 -0
  185. package/scripts/dep-guard-analyze.js +71 -0
  186. package/scripts/dep-guard.sh +789 -0
  187. package/scripts/eval_win.py +249 -0
  188. package/scripts/file-index.sh +493 -0
  189. package/scripts/forge-team/index.sh +86 -0
  190. package/scripts/forge-team/lib/agent-prompt.sh +52 -0
  191. package/scripts/forge-team/lib/claim.sh +256 -0
  192. package/scripts/forge-team/lib/dashboard.sh +341 -0
  193. package/scripts/forge-team/lib/epic.sh +332 -0
  194. package/scripts/forge-team/lib/hooks.sh +253 -0
  195. package/scripts/forge-team/lib/identity.sh +235 -0
  196. package/scripts/forge-team/lib/sync-github.sh +317 -0
  197. package/scripts/forge-team/lib/verify.sh +284 -0
  198. package/scripts/forge-team/lib/workload.sh +296 -0
  199. package/scripts/forge-team/tests/agent-prompt.test.sh +72 -0
  200. package/scripts/forge-team/tests/claim.test.sh +179 -0
  201. package/scripts/forge-team/tests/dashboard.test.sh +170 -0
  202. package/scripts/forge-team/tests/dispatcher.test.sh +79 -0
  203. package/scripts/forge-team/tests/epic.test.sh +176 -0
  204. package/scripts/forge-team/tests/hooks.test.sh +239 -0
  205. package/scripts/forge-team/tests/identity.test.sh +176 -0
  206. package/scripts/forge-team/tests/integration.test.sh +371 -0
  207. package/scripts/forge-team/tests/sync-github.test.sh +209 -0
  208. package/scripts/forge-team/tests/verify.test.sh +314 -0
  209. package/scripts/forge-team/tests/workflow-integration.test.sh +43 -0
  210. package/scripts/forge-team/tests/workload.test.sh +209 -0
  211. package/scripts/github-beads-sync/comment.mjs +64 -0
  212. package/scripts/github-beads-sync/config.mjs +148 -0
  213. package/scripts/github-beads-sync/github-api.mjs +131 -0
  214. package/scripts/github-beads-sync/index.mjs +332 -0
  215. package/scripts/github-beads-sync/label-mapper.mjs +54 -0
  216. package/scripts/github-beads-sync/mapping.mjs +78 -0
  217. package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
  218. package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
  219. package/scripts/github-beads-sync/run-bd.mjs +159 -0
  220. package/scripts/github-beads-sync/sanitize.mjs +121 -0
  221. package/scripts/github-beads-sync.config.json +26 -0
  222. package/scripts/improve-command.js +375 -0
  223. package/scripts/lib/eval-runner.js +268 -0
  224. package/scripts/lib/eval-schema.js +135 -0
  225. package/scripts/lib/eval-storage.js +78 -0
  226. package/scripts/lib/grading.js +203 -0
  227. package/scripts/lib/jsonl-lock.sh +48 -0
  228. package/scripts/lib/sanitize.sh +116 -0
  229. package/scripts/lib/transcript-parser.js +63 -0
  230. package/scripts/lint.js +47 -0
  231. package/scripts/migrate-to-bun-test.js +412 -0
  232. package/scripts/pr-coordinator.sh +706 -0
  233. package/scripts/run-command-eval.js +236 -0
  234. package/scripts/smart-status.sh +809 -0
  235. package/scripts/sync-commands.js +571 -0
  236. package/scripts/sync-utils.sh +455 -0
  237. package/scripts/test-dashboard.js +123 -0
  238. package/scripts/test.js +46 -0
  239. package/scripts/validate.sh +94 -0
  240. package/skills/parallel-deep-research/SKILL.md +108 -108
  241. package/skills/parallel-deep-research/evals/README.md +27 -27
  242. package/skills/parallel-deep-research/evals/evals.json +62 -62
  243. package/skills/sonarcloud-analysis/SKILL.md +171 -171
  244. package/skills/sonarcloud-analysis/evals/README.md +27 -27
  245. package/skills/sonarcloud-analysis/evals/evals.json +50 -50
  246. package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
  247. package/.cursor/hooks/state/continual-learning-index.json +0 -19
  248. package/.cursor/hooks/state/continual-learning.json +0 -8
@@ -0,0 +1,493 @@
1
+ #!/usr/bin/env bash
2
+ # file-index.sh — File index JSONL helpers for multi-developer awareness.
3
+ #
4
+ # Manages .beads/file-index.jsonl — an append-only log of which developer
5
+ # is working on which files/modules, keyed by issue_id.
6
+ #
7
+ # Functions (source this file):
8
+ # file_index_add <issue_id> <developer> <files_json> <modules_json>
9
+ # file_index_remove <issue_id>
10
+ # file_index_read
11
+ # file_index_get <issue_id>
12
+ # file_index_update_from_tasks <issue_id> <task_file_path> [action]
13
+ #
14
+ # Uses jq for all JSON construction (never string concatenation).
15
+ # OWASP A03: All inputs validated and shell-injection patterns stripped.
16
+
17
+ # Only set errexit/pipefail when run as a script, not when sourced
18
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
19
+ set -euo pipefail
20
+ fi
21
+
22
+ # Source shared libraries
23
+ _FI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ source "$_FI_SCRIPT_DIR/lib/sanitize.sh"
25
+ if [[ -f "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh" ]]; then
26
+ source "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh"
27
+ fi
28
+
29
+ # ── Helpers ──────────────────────────────────────────────────────────────
30
+
31
+ # Resolve the JSONL file path.
32
+ # FILE_INDEX_ROOT can be overridden for testing.
33
+ _file_index_path() {
34
+ local root="${FILE_INDEX_ROOT:-.}"
35
+ printf '%s' "$root/.beads/file-index.jsonl"
36
+ }
37
+
38
+ # Validate issue_id: alphanumeric + hyphens only
39
+ _validate_issue_id() {
40
+ local id="$1"
41
+ if [[ ! "$id" =~ ^[a-zA-Z0-9-]+$ ]]; then
42
+ echo "Error: invalid issue_id format: must be alphanumeric + hyphens only" >&2
43
+ return 1
44
+ fi
45
+ }
46
+
47
+ # Validate developer identity: ^[a-zA-Z0-9._@+-]+$
48
+ _validate_developer() {
49
+ local dev="$1"
50
+ if [[ ! "$dev" =~ ^[a-zA-Z0-9._@+\-]+$ ]]; then
51
+ echo "Error: invalid developer format: must match ^[a-zA-Z0-9._@+-]+$" >&2
52
+ return 1
53
+ fi
54
+ }
55
+
56
+ # ── Public functions ─────────────────────────────────────────────────────
57
+
58
+ # file_index_add <issue_id> <developer> <files_json> <modules_json>
59
+ # Append an entry to the file index JSONL.
60
+ # files_json and modules_json must be valid JSON arrays.
61
+ file_index_add() {
62
+ local raw_issue_id="$1"
63
+ local raw_developer="$2"
64
+ local files_json="$3"
65
+ local modules_json="$4"
66
+
67
+ # Sanitize inputs
68
+ local issue_id
69
+ issue_id="$(sanitize "$raw_issue_id")"
70
+ # Trim whitespace produced by sanitize
71
+ issue_id="$(printf '%s' "$issue_id" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
72
+
73
+ local developer
74
+ developer="$(sanitize "$raw_developer")"
75
+ developer="$(printf '%s' "$developer" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
76
+
77
+ # Validate formats (after sanitization — catches injection that was stripped)
78
+ _validate_issue_id "$issue_id" || return 1
79
+ _validate_developer "$developer" || return 1
80
+
81
+ # Validate files_json and modules_json are valid JSON arrays
82
+ if ! printf '%s' "$files_json" | jq 'if type == "array" then empty else error end' 2>/dev/null; then
83
+ echo "Error: files_json must be a valid JSON array" >&2
84
+ return 1
85
+ fi
86
+ if ! printf '%s' "$modules_json" | jq 'if type == "array" then empty else error end' 2>/dev/null; then
87
+ echo "Error: modules_json must be a valid JSON array" >&2
88
+ return 1
89
+ fi
90
+
91
+ # Generate timestamp
92
+ local updated_at
93
+ updated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
94
+
95
+ # Build JSON using jq (never string concatenation)
96
+ local json_line
97
+ json_line="$(jq -n -c \
98
+ --arg id "$issue_id" \
99
+ --arg dev "$developer" \
100
+ --argjson files "$files_json" \
101
+ --argjson modules "$modules_json" \
102
+ --arg ts "$updated_at" \
103
+ '{
104
+ issue_id: $id,
105
+ developer: $dev,
106
+ files: $files,
107
+ modules: $modules,
108
+ updated_at: $ts,
109
+ tombstone: false
110
+ }')"
111
+
112
+ # Append to JSONL file
113
+ local jsonl_path
114
+ jsonl_path="$(_file_index_path)"
115
+
116
+ # Ensure parent directory exists
117
+ mkdir -p "$(dirname "$jsonl_path")"
118
+
119
+ if command -v atomic_jsonl_append &>/dev/null; then
120
+ atomic_jsonl_append "$jsonl_path" "$json_line"
121
+ else
122
+ printf '%s\n' "$json_line" >> "$jsonl_path"
123
+ fi
124
+ }
125
+
126
+ # file_index_remove <issue_id>
127
+ # Append a tombstone entry for the given issue.
128
+ file_index_remove() {
129
+ local raw_issue_id="$1"
130
+
131
+ # Sanitize and validate
132
+ local issue_id
133
+ issue_id="$(sanitize "$raw_issue_id")"
134
+ issue_id="$(printf '%s' "$issue_id" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
135
+ _validate_issue_id "$issue_id" || return 1
136
+
137
+ # Generate timestamp
138
+ local updated_at
139
+ updated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
140
+
141
+ # Build tombstone JSON using jq
142
+ local json_line
143
+ json_line="$(jq -n -c \
144
+ --arg id "$issue_id" \
145
+ --arg ts "$updated_at" \
146
+ '{
147
+ issue_id: $id,
148
+ developer: "",
149
+ files: [],
150
+ modules: [],
151
+ updated_at: $ts,
152
+ tombstone: true
153
+ }')"
154
+
155
+ # Append to JSONL file
156
+ local jsonl_path
157
+ jsonl_path="$(_file_index_path)"
158
+ mkdir -p "$(dirname "$jsonl_path")"
159
+ if command -v atomic_jsonl_append &>/dev/null; then
160
+ atomic_jsonl_append "$jsonl_path" "$json_line"
161
+ else
162
+ printf '%s\n' "$json_line" >> "$jsonl_path"
163
+ fi
164
+ }
165
+
166
+ # file_index_read
167
+ # Read all entries, resolve LWW per issue_id, output active entries as JSON array.
168
+ # Returns "[]" if the file is missing or empty.
169
+ file_index_read() {
170
+ local jsonl_path
171
+ jsonl_path="$(_file_index_path)"
172
+
173
+ # Handle missing or empty file
174
+ if [[ ! -f "$jsonl_path" ]] || [[ ! -s "$jsonl_path" ]]; then
175
+ printf '%s' "[]"
176
+ return 0
177
+ fi
178
+
179
+ # Use jq to:
180
+ # 1. Slurp all lines into an array
181
+ # 2. Group by issue_id
182
+ # 3. For each group, sort by updated_at and take the last (LWW)
183
+ # 4. Filter out tombstoned entries
184
+ jq -s -c '
185
+ sort_by(.issue_id)
186
+ | group_by(.issue_id)
187
+ | map(sort_by(.updated_at) | last)
188
+ | map(select(.tombstone == false))
189
+ ' "$jsonl_path"
190
+ }
191
+
192
+ # file_index_get <issue_id>
193
+ # Get a single issue's file entry (after LWW resolution).
194
+ # Returns "null" if not found or tombstoned.
195
+ file_index_get() {
196
+ local raw_issue_id="$1"
197
+
198
+ # Sanitize and validate
199
+ local issue_id
200
+ issue_id="$(sanitize "$raw_issue_id")"
201
+ issue_id="$(printf '%s' "$issue_id" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
202
+ _validate_issue_id "$issue_id" || return 1
203
+
204
+ local jsonl_path
205
+ jsonl_path="$(_file_index_path)"
206
+
207
+ # Handle missing or empty file
208
+ if [[ ! -f "$jsonl_path" ]] || [[ ! -s "$jsonl_path" ]]; then
209
+ printf '%s' "null"
210
+ return 0
211
+ fi
212
+
213
+ # Use jq to filter by issue_id, resolve LWW, check tombstone
214
+ jq -s -c --arg id "$issue_id" '
215
+ map(select(.issue_id == $id))
216
+ | sort_by(.updated_at)
217
+ | last
218
+ | if . == null then null
219
+ elif .tombstone == true then null
220
+ else .
221
+ end
222
+ ' "$jsonl_path"
223
+ }
224
+
225
+ # file_index_update_from_tasks <issue_id> <task_file_path> [action]
226
+ # Parse a task file for File(s): lines, extract file paths, derive modules,
227
+ # and update the file index using the current session identity.
228
+ #
229
+ # action: "in_progress" (default) — adds/updates entry
230
+ # "closed" — appends tombstone via file_index_remove
231
+ #
232
+ # Edge cases:
233
+ # - Missing task file or no File(s): lines → fallback with confidence: "low"
234
+ # - File paths are sanitized against shell injection (OWASP A03)
235
+ file_index_update_from_tasks() {
236
+ local raw_issue_id="$1"
237
+ local task_file_path="${2:-}"
238
+ local action="${3:-in_progress}"
239
+
240
+ # Sanitize and validate issue_id
241
+ local issue_id
242
+ issue_id="$(sanitize "$raw_issue_id")"
243
+ issue_id="$(printf '%s' "$issue_id" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
244
+ _validate_issue_id "$issue_id" || return 1
245
+
246
+ # Handle closed action: just tombstone
247
+ if [[ "$action" == "closed" ]]; then
248
+ file_index_remove "$issue_id"
249
+ return 0
250
+ fi
251
+
252
+ # Get session identity (source sync-utils.sh if get_session_identity not defined)
253
+ local developer
254
+ if ! command -v get_session_identity &>/dev/null; then
255
+ local script_dir
256
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
257
+ if [[ -f "$script_dir/sync-utils.sh" ]]; then
258
+ source "$script_dir/sync-utils.sh"
259
+ fi
260
+ fi
261
+ developer="$(get_session_identity)" || {
262
+ echo "Error: could not determine session identity" >&2
263
+ return 1
264
+ }
265
+
266
+ # Parse task file for File(s): lines
267
+ local -a raw_files=()
268
+ local confidence="high"
269
+
270
+ if [[ -z "$task_file_path" ]] || [[ ! -f "$task_file_path" ]]; then
271
+ # Fallback: no task file
272
+ confidence="low"
273
+ else
274
+ # Extract backtick-quoted paths from File(s): lines (case-insensitive)
275
+ local line
276
+ while IFS= read -r line; do
277
+ # Extract all backtick-quoted strings from this line
278
+ local path
279
+ while [[ "$line" =~ \`([^\`]+)\` ]]; do
280
+ path="${BASH_REMATCH[1]}"
281
+ # Strip only trailing annotations like " (extend)" at end of path
282
+ # Anchored to $ to avoid corrupting paths with parentheses (e.g., test(1).spec.ts)
283
+ path="$(printf '%s' "$path" | sed 's/[[:space:]]*([^)]*)$//')"
284
+ # Trim whitespace
285
+ path="$(printf '%s' "$path" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
286
+ if [[ -n "$path" ]]; then
287
+ raw_files+=("$path")
288
+ fi
289
+ # Remove the matched backtick segment — use fixed pattern to avoid
290
+ # glob interpretation of path characters like [ ] * ?
291
+ line="${line#*\`}" # strip up to and including the opening backtick
292
+ line="${line#*\`}" # strip the content and closing backtick
293
+ done
294
+ done < <(grep -i '^File(s):' "$task_file_path" 2>/dev/null || true)
295
+
296
+ # If no File(s): lines found, fall back
297
+ if [[ ${#raw_files[@]} -eq 0 ]]; then
298
+ confidence="low"
299
+ fi
300
+ fi
301
+
302
+ # Sanitize file paths, derive modules, deduplicate via jq (Bash 3.2 compatible)
303
+ local sanitized_paths=""
304
+
305
+ for raw_path in "${raw_files[@]}"; do
306
+ # OWASP A03: reject paths with shell injection patterns
307
+ if printf '%s' "$raw_path" | grep -qE '(\$\(|`|;|\||\&\&|\|\|)'; then
308
+ continue
309
+ fi
310
+ # Skip paths with only dots/slashes (directory traversal)
311
+ if [[ "$raw_path" =~ ^[./]+$ ]]; then
312
+ continue
313
+ fi
314
+ sanitized_paths="${sanitized_paths}${raw_path}"$'\n'
315
+ done
316
+
317
+ # Build deduplicated JSON arrays using jq (no associative arrays needed)
318
+ local files_json modules_json
319
+ if [[ -n "$sanitized_paths" ]]; then
320
+ # Save and restore pipefail state to avoid side effects
321
+ local _prev_pipefail
322
+ _prev_pipefail="$(set +o | grep pipefail)" || true
323
+ set -o pipefail
324
+
325
+ files_json="$(printf '%s' "$sanitized_paths" | grep -v '^$' | jq -R . | jq -s -c 'unique')" || {
326
+ eval "$_prev_pipefail"
327
+ echo "Error: failed to build files JSON array" >&2
328
+ return 1
329
+ }
330
+ modules_json="$(printf '%s' "$sanitized_paths" | grep -v '^$' | while IFS= read -r p; do
331
+ local d
332
+ d="$(dirname "$p")"
333
+ if [[ "$d" == "." ]]; then printf '%s\n' "./"; else printf '%s\n' "${d}/"; fi
334
+ done | jq -R . | jq -s -c 'unique')" || {
335
+ eval "$_prev_pipefail"
336
+ echo "Error: failed to build modules JSON array" >&2
337
+ return 1
338
+ }
339
+
340
+ # Restore pipefail state
341
+ eval "$_prev_pipefail"
342
+ else
343
+ files_json="[]"
344
+ modules_json="[]"
345
+ fi
346
+
347
+ # Build and append the entry
348
+ local updated_at
349
+ updated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
350
+
351
+ local jsonl_path
352
+ jsonl_path="$(_file_index_path)"
353
+ mkdir -p "$(dirname "$jsonl_path")"
354
+
355
+ if [[ "$confidence" == "low" ]]; then
356
+ # Include confidence field for fallback entries
357
+ local json_line
358
+ json_line="$(jq -n -c \
359
+ --arg id "$issue_id" \
360
+ --arg dev "$developer" \
361
+ --argjson files "$files_json" \
362
+ --argjson modules "$modules_json" \
363
+ --arg ts "$updated_at" \
364
+ --arg conf "$confidence" \
365
+ '{
366
+ issue_id: $id,
367
+ developer: $dev,
368
+ files: $files,
369
+ modules: $modules,
370
+ updated_at: $ts,
371
+ tombstone: false,
372
+ confidence: $conf
373
+ }')"
374
+ if command -v atomic_jsonl_append &>/dev/null; then
375
+ atomic_jsonl_append "$jsonl_path" "$json_line"
376
+ else
377
+ printf '%s\n' "$json_line" >> "$jsonl_path"
378
+ fi
379
+ else
380
+ # Normal entry via file_index_add (no confidence field needed)
381
+ file_index_add "$issue_id" "$developer" "$files_json" "$modules_json"
382
+ fi
383
+ }
384
+
385
+ # file_index_auto_update <issue_id> [--from-git]
386
+ # Update the file index with the current set of changed files.
387
+ # Files come from: --from-git flag (git diff HEAD~1) or stdin.
388
+ file_index_auto_update() {
389
+ local raw_issue_id="$1"
390
+ shift
391
+ local from_git=0
392
+
393
+ # Parse flags
394
+ while [[ $# -gt 0 ]]; do
395
+ case "$1" in
396
+ --from-git) from_git=1; shift ;;
397
+ *) echo "Error: unknown flag '$1'" >&2; return 1 ;;
398
+ esac
399
+ done
400
+
401
+ # Sanitize and validate issue_id
402
+ local issue_id
403
+ issue_id="$(sanitize "$raw_issue_id")"
404
+ issue_id="$(printf '%s' "$issue_id" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
405
+ _validate_issue_id "$issue_id" || return 1
406
+
407
+ # Get file list
408
+ local -a files=()
409
+ if [[ "$from_git" -eq 1 ]]; then
410
+ while IFS= read -r f; do
411
+ [[ -n "$f" ]] && files+=("$f")
412
+ done < <(git diff --name-only HEAD~1 2>/dev/null || true)
413
+ else
414
+ # Read from stdin (non-blocking check)
415
+ if [[ ! -t 0 ]]; then
416
+ while IFS= read -r f; do
417
+ [[ -n "$f" ]] && files+=("$f")
418
+ done
419
+ else
420
+ echo "Error: no file list provided (use --from-git or pipe files via stdin)" >&2
421
+ return 1
422
+ fi
423
+ fi
424
+
425
+ # Empty file list = nothing to do
426
+ if [[ ${#files[@]} -eq 0 ]]; then
427
+ echo "No files changed"
428
+ return 0
429
+ fi
430
+
431
+ # Get session identity
432
+ local developer
433
+ if ! command -v get_session_identity &>/dev/null; then
434
+ local script_dir
435
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
436
+ if [[ -f "$script_dir/sync-utils.sh" ]]; then
437
+ source "$script_dir/sync-utils.sh"
438
+ fi
439
+ fi
440
+ developer="$(get_session_identity)" || {
441
+ echo "Error: could not determine session identity" >&2
442
+ return 1
443
+ }
444
+
445
+ # Build JSON arrays using jq
446
+ local files_json modules_json
447
+ files_json="$(printf '%s\n' "${files[@]}" | jq -R . | jq -s -c 'unique')"
448
+ modules_json="$(printf '%s\n' "${files[@]}" | while IFS= read -r p; do
449
+ local d
450
+ d="$(dirname "$p")"
451
+ if [[ "$d" == "." ]]; then printf '%s\n' "./"; else printf '%s\n' "${d}/"; fi
452
+ done | jq -R . | jq -s -c 'unique')"
453
+
454
+ # Append via file_index_add (which uses atomic locking)
455
+ file_index_add "$issue_id" "$developer" "$files_json" "$modules_json"
456
+
457
+ echo "File index updated for $issue_id (${#files[@]} files)"
458
+ }
459
+
460
+ # ── Main dispatcher (when run as a script) ────────────────────────────────
461
+
462
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
463
+ if [[ $# -lt 1 ]]; then
464
+ cat >&2 <<'EOF'
465
+ Usage: file-index.sh <subcommand> [args...]
466
+
467
+ Subcommands:
468
+ add <issue_id> <developer> <files_json> <modules_json>
469
+ remove <issue_id>
470
+ read
471
+ get <issue_id>
472
+ update-from-tasks <issue_id> <task_file_path> [action]
473
+ auto-update <issue_id> [--from-git]
474
+ EOF
475
+ exit 1
476
+ fi
477
+
478
+ subcommand="$1"
479
+ shift
480
+
481
+ case "$subcommand" in
482
+ add) file_index_add "$@" ;;
483
+ remove) file_index_remove "$@" ;;
484
+ read) file_index_read ;;
485
+ get) file_index_get "$@" ;;
486
+ update-from-tasks) file_index_update_from_tasks "$@" ;;
487
+ auto-update) file_index_auto_update "$@" ;;
488
+ *)
489
+ echo "Error: Unknown subcommand '${subcommand}'" >&2
490
+ exit 1
491
+ ;;
492
+ esac
493
+ fi
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env bash
2
+ # forge-team — Team orchestration plugin for Forge.
3
+ #
4
+ # Subcommands:
5
+ # workload Show team workload by developer
6
+ # epic Epic progress rollup
7
+ # dashboard Team health dashboard
8
+ # add Add developer to team map
9
+ # verify Check 1:1 Beads<>GitHub enforcement
10
+ # sync Manual GitHub<>Beads sync
11
+ # claim Claim an issue with pre-check
12
+ # help Show usage
13
+ #
14
+ # Exit codes: 0=success, 1=error, 2=validation error
15
+
16
+ set -euo pipefail
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+
20
+ # Source libs (graceful if missing — stubs used in early dev)
21
+ [[ -f "$SCRIPT_DIR/lib/agent-prompt.sh" ]] && source "$SCRIPT_DIR/lib/agent-prompt.sh"
22
+ [[ -f "$SCRIPT_DIR/lib/identity.sh" ]] && source "$SCRIPT_DIR/lib/identity.sh"
23
+ [[ -f "$SCRIPT_DIR/lib/workload.sh" ]] && source "$SCRIPT_DIR/lib/workload.sh"
24
+ [[ -f "$SCRIPT_DIR/lib/epic.sh" ]] && source "$SCRIPT_DIR/lib/epic.sh"
25
+ [[ -f "$SCRIPT_DIR/lib/dashboard.sh" ]] && source "$SCRIPT_DIR/lib/dashboard.sh"
26
+ [[ -f "$SCRIPT_DIR/lib/hooks.sh" ]] && source "$SCRIPT_DIR/lib/hooks.sh"
27
+ [[ -f "$SCRIPT_DIR/lib/verify.sh" ]] && source "$SCRIPT_DIR/lib/verify.sh"
28
+ [[ -f "$SCRIPT_DIR/lib/claim.sh" ]] && source "$SCRIPT_DIR/lib/claim.sh"
29
+
30
+ # Source shared forge utilities
31
+ FORGE_SCRIPTS="$(cd "$SCRIPT_DIR/.." && pwd)"
32
+ [[ -f "$FORGE_SCRIPTS/lib/sanitize.sh" ]] && source "$FORGE_SCRIPTS/lib/sanitize.sh"
33
+
34
+ # ── Stub implementations ──
35
+ # cmd_workload provided by lib/workload.sh
36
+ # cmd_epic provided by lib/epic.sh
37
+ # cmd_dashboard provided by lib/dashboard.sh
38
+ cmd_add() { auto_detect_identity "$@"; }
39
+ # cmd_verify provided by lib/verify.sh
40
+ cmd_sync() { forge_team_sync "$@"; }
41
+ cmd_claim() { forge_team_claim "$@"; }
42
+
43
+ cmd_help() {
44
+ cat <<'EOF'
45
+ Usage: forge team <subcommand> [args...]
46
+
47
+ Subcommands:
48
+ workload [--developer=<user>] [--me] Show team workload
49
+ epic <issue-id> Epic progress rollup
50
+ dashboard Team health dashboard
51
+ add [--github=<user>] Add developer to team map
52
+ verify Check 1:1 Beads<>GitHub sync
53
+ sync Manual GitHub<>Beads sync
54
+ claim <issue-id> [--force] Claim issue with pre-check
55
+ help Show this message
56
+ EOF
57
+ }
58
+
59
+ # ── Main dispatcher ──
60
+ main() {
61
+ if [[ $# -lt 1 ]]; then
62
+ cmd_help >&2
63
+ exit 1
64
+ fi
65
+
66
+ local subcommand="$1"
67
+ shift
68
+
69
+ case "$subcommand" in
70
+ workload) cmd_workload "$@" ;;
71
+ epic) cmd_epic "$@" ;;
72
+ dashboard) cmd_dashboard "$@" ;;
73
+ add) cmd_add "$@" ;;
74
+ verify) cmd_verify "$@" ;;
75
+ sync) cmd_sync "$@" ;;
76
+ claim) cmd_claim "$@" ;;
77
+ help) cmd_help ;;
78
+ *)
79
+ echo "Error: unknown subcommand '$subcommand'" >&2
80
+ cmd_help >&2
81
+ exit 1
82
+ ;;
83
+ esac
84
+ }
85
+
86
+ main "$@"
@@ -0,0 +1,52 @@
1
+ # agent-prompt.sh — Shared library for structured agent communication
2
+ # All user-facing output from `forge team` goes through these helpers.
3
+ # Uses non-guessable prefix to prevent injection from GitHub data.
4
+ #
5
+ # Usage: source this file from other scripts.
6
+ # This library does NOT set errexit/pipefail.
7
+
8
+ # Guard against double-sourcing (readonly would error on second source)
9
+ if [[ -n "${_AGENT_PROMPT_LIB_LOADED:-}" ]]; then
10
+ return 0 2>/dev/null || true
11
+ fi
12
+ _AGENT_PROMPT_LIB_LOADED=1
13
+
14
+ readonly FORGE_AGENT_PREFIX="FORGE_AGENT_7f3a"
15
+
16
+ # Output a prompt for the AI agent to ask the user.
17
+ # Format: FORGE_AGENT_7f3a:PROMPT: <message> (to stderr)
18
+ agent_prompt() {
19
+ echo "${FORGE_AGENT_PREFIX}:PROMPT: $1" >&2
20
+ }
21
+
22
+ # Informational output.
23
+ # Format: FORGE_AGENT_7f3a:INFO: <message> (to stderr)
24
+ agent_info() {
25
+ echo "${FORGE_AGENT_PREFIX}:INFO: $1" >&2
26
+ }
27
+
28
+ # Error output.
29
+ # Format: FORGE_AGENT_7f3a:ERROR: <message> (to stderr)
30
+ agent_error() {
31
+ echo "${FORGE_AGENT_PREFIX}:ERROR: $1" >&2
32
+ }
33
+
34
+ # Strip any occurrence of the FORGE_AGENT_7f3a: prefix from input strings.
35
+ # Prevents injection from GitHub issue titles or other external data.
36
+ # Takes input via stdin or as $1 arg, outputs sanitized text to stdout.
37
+ sanitize_for_agent() {
38
+ local input
39
+ if [[ $# -gt 0 ]]; then
40
+ input="$1"
41
+ else
42
+ input="$(cat)"
43
+ fi
44
+ # Loop until no more prefix occurrences (prevents split-prefix bypass)
45
+ local prev
46
+ while true; do
47
+ prev="$input"
48
+ input="${input//${FORGE_AGENT_PREFIX}:/}"
49
+ [[ "$input" == "$prev" ]] && break
50
+ done
51
+ printf '%s' "$input"
52
+ }