@codedrifters/configulator 0.0.382 → 0.0.384

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/lib/index.mjs CHANGED
@@ -13891,6 +13891,16 @@ var DEFAULT_BUNDLE_OVERRIDES = {
13891
13891
  "meeting:link": {
13892
13892
  acceptanceCriteria: { smallMax: 3, mediumMax: 15 },
13893
13893
  sources: { smallMax: 2, mediumMax: 10 }
13894
+ },
13895
+ "type:docs": {
13896
+ acceptanceCriteria: { smallMax: 3, mediumMax: 12 }
13897
+ },
13898
+ "bcm:context": {
13899
+ acceptanceCriteria: { smallMax: 3, mediumMax: 12 }
13900
+ },
13901
+ "req:scan": {
13902
+ acceptanceCriteria: { smallMax: 3, mediumMax: 12 },
13903
+ sources: { smallMax: 2, mediumMax: 12 }
13894
13904
  }
13895
13905
  };
13896
13906
  var DEFAULT_DECOMPOSITION_TEMPLATE = [
@@ -14939,6 +14949,55 @@ function shellSingleQuote(value) {
14939
14949
  }
14940
14950
 
14941
14951
  // src/agent/bundles/orchestrator.ts
14952
+ function renderDelegationActiveSignalsHelper() {
14953
+ return [
14954
+ "# delegation_active_signals <pr-number> <head-oid>",
14955
+ "# Echo a \\x1f-separated triple: <fixlist_at><US><report_at><US><head_at>",
14956
+ "# (US = ASCII Unit Separator; see the printf below for why not a tab).",
14957
+ "# Each field is an ISO-8601 timestamp or empty. Shared by the",
14958
+ "# feedback-drain (cmd_needs_worker) and the lease-reconcile",
14959
+ "# (cmd_lease_reconcile) so both judge a delegation 'active' the same",
14960
+ "# way. See the block comment above the renderer for field semantics.",
14961
+ "delegation_active_signals() {",
14962
+ ' local pr_num="$1"',
14963
+ ' local head_oid="${2:-}"',
14964
+ "",
14965
+ " # Pull the PR comments once; derive both the fix-list reference time",
14966
+ " # and the newest worker-report time from the same payload.",
14967
+ " local comments",
14968
+ ' comments=$(gh pr view "$pr_num" --json comments \\',
14969
+ ` --jq '.comments' 2>/dev/null || echo "[]")`,
14970
+ "",
14971
+ " # Newest fix-list comment createdAt (empty if none found).",
14972
+ " local fixlist_at",
14973
+ ` fixlist_at=$(echo "$comments" | jq -r '`,
14974
+ ' [.[] | select(.body | contains("## Reviewer: fix list for @issue-worker"))]',
14975
+ ` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
14976
+ "",
14977
+ " # Newest worker-report createdAt (empty if none found). Keyed off the",
14978
+ " # stable HTML marker complete-feedback-handoff.sh embeds.",
14979
+ " local report_at",
14980
+ ` report_at=$(echo "$comments" | jq -r '`,
14981
+ ' [.[] | select(.body | contains("issue-worker feedback hand-off \u2014 review:needs-worker cleared"))]',
14982
+ ` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
14983
+ "",
14984
+ " # Branch HEAD committer date (empty when no head-oid or lookup fails).",
14985
+ ' local head_at=""',
14986
+ ' if [[ -n "$head_oid" ]]; then',
14987
+ ' head_at=$(gh api "repos/{owner}/{repo}/commits/${head_oid}" \\',
14988
+ ` --jq '.commit.committer.date // ""' 2>/dev/null || echo "")`,
14989
+ " fi",
14990
+ "",
14991
+ " # Emit the triple delimited by the ASCII Unit Separator (\\x1f), NOT a",
14992
+ " # tab: any field may be empty, and `read` with a whitespace IFS (tab",
14993
+ " # counts) collapses adjacent empty fields \u2014 which would shift head_at",
14994
+ " # into report_at. \\x1f is non-whitespace and never appears in a",
14995
+ " # timestamp, so callers `IFS=$'\\x1f' read -r fixlist_at report_at",
14996
+ " # head_at` recover all three fields even when the middle one is empty.",
14997
+ ` printf '%s\\x1f%s\\x1f%s\\n' "$fixlist_at" "$report_at" "$head_at"`,
14998
+ "}"
14999
+ ];
15000
+ }
14942
15001
  function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
14943
15002
  const tierCase = renderAgentTierCaseStatement(tiers);
14944
15003
  const scopeHelper = renderScopeGateShellHelpers(scopeGate);
@@ -15001,12 +15060,20 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15001
15060
  "",
15002
15061
  scopeHelperIndented,
15003
15062
  "",
15063
+ ...renderDelegationActiveSignalsHelper(),
15064
+ "",
15004
15065
  "# \u2500\u2500 subcommands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
15005
15066
  "",
15006
15067
  "cmd_unblock() {",
15068
+ " # Fetch ALL open status:blocked issues, not just the first page (#823).",
15069
+ " # This is a label-filtered `gh issue list` (REST issues endpoint, which gh",
15070
+ " # paginates up to --limit) \u2014 NOT a --search query \u2014 so a large ceiling is",
15071
+ " # safe from the search-API secondary rate limits that cap unblock-dependents",
15072
+ " # at 100. An all-deps-closed issue must be reachable regardless of backlog",
15073
+ " # position.",
15007
15074
  " local issues",
15008
15075
  ' issues=$(gh issue list --label "status:blocked" --state open \\',
15009
- ' --json number,body --limit 50 2>/dev/null || echo "[]")',
15076
+ ' --json number,body --limit 1000 2>/dev/null || echo "[]")',
15010
15077
  "",
15011
15078
  " local count",
15012
15079
  ` count=$(echo "$issues" | jq 'length')`,
@@ -15174,9 +15241,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15174
15241
  "",
15175
15242
  "cmd_stale() {",
15176
15243
  " # Check in-progress issues",
15244
+ " # (#823) cover all in-progress issues, not just the first 50",
15177
15245
  " local ip_issues",
15178
15246
  ' ip_issues=$(gh issue list --label "status:in-progress" --state open \\',
15179
- ' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
15247
+ ' --json number,title,updatedAt --limit 1000 2>/dev/null || echo "[]")',
15180
15248
  "",
15181
15249
  " local ip_count",
15182
15250
  ` ip_count=$(echo "$ip_issues" | jq 'length')`,
@@ -15201,9 +15269,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15201
15269
  " fi",
15202
15270
  "",
15203
15271
  " # Check blocked issues",
15272
+ " # (#823) cover all blocked issues, not just the first 50",
15204
15273
  " local bl_issues",
15205
15274
  ' bl_issues=$(gh issue list --label "status:blocked" --state open \\',
15206
- ' --json number,title,updatedAt --limit 50 2>/dev/null || echo "[]")',
15275
+ ' --json number,title,updatedAt,body --limit 1000 2>/dev/null || echo "[]")',
15207
15276
  "",
15208
15277
  " local bl_count",
15209
15278
  ` bl_count=$(echo "$bl_issues" | jq 'length')`,
@@ -15214,13 +15283,36 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15214
15283
  ' || date -u -d "${STALE_BLOCKED_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
15215
15284
  "",
15216
15285
  " local bl_data",
15217
- ` bl_data=$(echo "$bl_issues" | jq -r '.[] | "\\(.number)\\t\\(.title)\\t\\(.updatedAt)"')`,
15286
+ ` bl_data=$(echo "$bl_issues" | jq -r '`,
15287
+ " .[] |",
15288
+ ' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
15289
+ ' "\\(.number)\\t\\(.title)\\t\\(.updatedAt)\\t\\($dep_line)"',
15290
+ " ')",
15218
15291
  "",
15219
- " while IFS=$'\\t' read -r num title updated; do",
15292
+ " while IFS=$'\\t' read -r num title updated dep_line; do",
15220
15293
  ' [[ -z "$num" ]] && continue',
15221
15294
  ' local updated_trimmed="${updated%%+*}"',
15222
15295
  ' updated_trimmed="${updated_trimmed%%Z*}"',
15223
15296
  ' if [[ "$updated_trimmed" < "$bl_threshold" ]]; then',
15297
+ " # (#822) Only flag a blocked issue with NO resolvable open dependency.",
15298
+ " # A parseable `Depends on: #N` naming any OPEN issue means the block is",
15299
+ " # legitimate phase-chain progress (the unblock sweep clears it when the",
15300
+ " # dep closes) \u2014 do NOT flag. Reserve the >7d flag for issues with no",
15301
+ " # `Depends on:` line, or whose deps are all closed yet still blocked.",
15302
+ ' local deps=""',
15303
+ ' [[ -n "$dep_line" ]] && deps=$(parse_deps "$dep_line")',
15304
+ " local has_open_dep=false",
15305
+ ' if [[ -n "${deps// /}" ]]; then',
15306
+ " for dep in $deps; do",
15307
+ ' if ! is_closed "$dep"; then',
15308
+ " has_open_dep=true",
15309
+ " break",
15310
+ " fi",
15311
+ " done",
15312
+ " fi",
15313
+ " if $has_open_dep; then",
15314
+ " continue",
15315
+ " fi",
15224
15316
  ' local date_part="${updated_trimmed%%T*}"',
15225
15317
  ' echo "STALE_BLOCKED #${num} \u2014 blocked since ${date_part} \u2014 \\"${title}\\""',
15226
15318
  " fi",
@@ -15317,7 +15409,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15317
15409
  " # `review:fixing` lease is left for that confirm pass to release,",
15318
15410
  " # mirroring complete-feedback-handoff.sh.",
15319
15411
  " #",
15320
- " # Signals (all read via gh/jq):",
15412
+ " # Signals \u2014 gathered by the shared `delegation_active_signals`",
15413
+ " # helper (defined once, embedded here AND in pr-sweep.sh's",
15414
+ " # cmd_needs_worker) so the reconcile and the feedback-drain judge a",
15415
+ " # delegation 'active' identically:",
15321
15416
  " # - fix-list comment: the reviewer's most recent PR comment whose",
15322
15417
  " # body contains the stable heading `## Reviewer: fix list for",
15323
15418
  " # @issue-worker`. Its createdAt is the lease's reference time.",
@@ -15326,7 +15421,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15326
15421
  " # `<!-- issue-worker feedback hand-off \u2014 review:needs-worker",
15327
15422
  " # cleared -->` posted by complete-feedback-handoff.sh. Presence +",
15328
15423
  " # createdAt newer than the fix-list means a result landed.",
15329
- " # - branch HEAD: the PR head commit committedDate, compared against",
15424
+ " # - branch HEAD: the PR head commit committer date, compared against",
15330
15425
  " # the fix-list createdAt to detect a push that advanced past the",
15331
15426
  " # delegation without a hand-off.",
15332
15427
  "",
@@ -15363,24 +15458,14 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15363
15458
  " while IFS=$'\\t' read -r pr_num has_marker head_oid; do",
15364
15459
  ' [[ -z "$pr_num" ]] && continue',
15365
15460
  "",
15366
- " # Pull the PR comments once; derive both the fix-list reference time",
15367
- " # and the newest worker-report time from the same payload.",
15368
- " local comments",
15369
- ' comments=$(gh pr view "$pr_num" --json comments \\',
15370
- ` --jq '.comments' 2>/dev/null || echo "[]")`,
15371
- "",
15372
- " # Newest fix-list comment createdAt (empty if none found).",
15373
- " local fixlist_at",
15374
- ` fixlist_at=$(echo "$comments" | jq -r '`,
15375
- ' [.[] | select(.body | contains("## Reviewer: fix list for @issue-worker"))]',
15376
- ` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
15377
- "",
15378
- " # Newest worker-report createdAt (empty if none found). Keyed off the",
15379
- " # stable HTML marker complete-feedback-handoff.sh embeds.",
15380
- " local report_at",
15381
- ` report_at=$(echo "$comments" | jq -r '`,
15382
- ' [.[] | select(.body | contains("issue-worker feedback hand-off \u2014 review:needs-worker cleared"))]',
15383
- ` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
15461
+ " # Gather the shared delegation-active signals in one call \u2014 the same",
15462
+ " # helper the feedback-drain (cmd_needs_worker) uses, so the two never",
15463
+ " # disagree on whether a delegation is demonstrably active. Yields the",
15464
+ " # fix-list reference time, the newest worker-report time, and the",
15465
+ " # branch HEAD committer date.",
15466
+ " local signals fixlist_at report_at head_at",
15467
+ ' signals=$(delegation_active_signals "$pr_num" "$head_oid")',
15468
+ ` IFS=$'\\x1f' read -r fixlist_at report_at head_at <<< "$signals"`,
15384
15469
  "",
15385
15470
  " # No fix-list comment at all \u2014 we cannot reason about this lease",
15386
15471
  " # (e.g. a human applied `review:fixing` by hand). Leave it alone.",
@@ -15399,9 +15484,6 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15399
15484
  " # Marker still set + no worker-report. Reconcile only when the",
15400
15485
  " # branch advanced past the fix-list (the worker pushed but skipped",
15401
15486
  " # the hand-off); otherwise this is a normal in-flight delegation.",
15402
- " local head_at",
15403
- ' head_at=$(gh api "repos/{owner}/{repo}/commits/${head_oid}" \\',
15404
- ` --jq '.commit.committer.date // ""' 2>/dev/null || echo "")`,
15405
15487
  ' if [[ -n "$head_at" && "$head_at" > "$fixlist_at" ]]; then',
15406
15488
  ' if gh pr edit "$pr_num" --remove-label "review:needs-worker" >/dev/null 2>&1; then',
15407
15489
  ' gh pr comment "$pr_num" \\',
@@ -15475,6 +15557,16 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15475
15557
  " # Extract the issue number after the '#' and before the ' '.",
15476
15558
  " local num=${line#STALE #}",
15477
15559
  " num=${num%% *}",
15560
+ " # (#822) Idempotent: never re-apply the human-clear-only flag to an",
15561
+ " # issue that already carries status:needs-attention.",
15562
+ " local existing_labels",
15563
+ ' existing_labels=$(gh issue view "$num" --json labels \\',
15564
+ ` --jq '.labels | map(.name) | index("status:needs-attention") != null' 2>/dev/null || echo "false")`,
15565
+ ' if [[ "$existing_labels" == "true" ]]; then',
15566
+ ' echo "SKIP_FLAGGED #${num} \u2014 already status:needs-attention (in-progress)"',
15567
+ ' echo "$line"',
15568
+ " continue",
15569
+ " fi",
15478
15570
  ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
15479
15571
  ' gh issue comment "$num" \\',
15480
15572
  ' --body "Flagged: in-progress for >3 days with no activity." >/dev/null 2>&1 || true',
@@ -15489,9 +15581,19 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
15489
15581
  " 'STALE_BLOCKED #'*)",
15490
15582
  " local num=${line#STALE_BLOCKED #}",
15491
15583
  " num=${num%% *}",
15584
+ " # (#822) Idempotent: never re-apply the human-clear-only flag to an",
15585
+ " # issue that already carries status:needs-attention.",
15586
+ " local existing_labels",
15587
+ ' existing_labels=$(gh issue view "$num" --json labels \\',
15588
+ ` --jq '.labels | map(.name) | index("status:needs-attention") != null' 2>/dev/null || echo "false")`,
15589
+ ' if [[ "$existing_labels" == "true" ]]; then',
15590
+ ' echo "SKIP_FLAGGED #${num} \u2014 already status:needs-attention (blocked)"',
15591
+ ' echo "$line"',
15592
+ " continue",
15593
+ " fi",
15492
15594
  ' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
15493
15595
  ' gh issue comment "$num" \\',
15494
- ' --body "Flagged: blocked for >7 days \u2014 may need human intervention." >/dev/null 2>&1 || true',
15596
+ ' --body "Flagged: blocked for >7 days with no resolvable dependency \u2014 may need human intervention." >/dev/null 2>&1 || true',
15495
15597
  ' echo "FLAGGED_BLOCKED #${num} \u2014 added status:needs-attention"',
15496
15598
  " flagged_blocked_count=$((flagged_blocked_count + 1))",
15497
15599
  " else",
@@ -15752,11 +15854,12 @@ function buildPrSweepScript() {
15752
15854
  "# PR_ELIGIBLE / PR_SKIP line per open PR; the",
15753
15855
  "# orchestrator dispatches `pr-reviewer` per eligible PR.",
15754
15856
  "# needs-worker \u2014 Phase B1 feedback-drain. Emits one DRAIN line per",
15755
- "# open PR carrying the `review:needs-worker` marker that",
15756
- "# is NOT already in-flight (no `review:fixing` lease),",
15857
+ "# open PR carrying the `review:needs-worker` marker,",
15757
15858
  "# ordered highest-priority-first so the orchestrator",
15758
15859
  "# dispatches `issue-worker` per PR to consume the",
15759
- "# reviewer's fix-list.",
15860
+ "# reviewer's fix-list. A PR that also holds the",
15861
+ "# `review:fixing` lease still drains unless a worker is",
15862
+ "# demonstrably active (see the needs-worker skip rules).",
15760
15863
  "#",
15761
15864
  "# Review-mode skip rules:",
15762
15865
  "# - isDraft = true \u2192 reason=draft",
@@ -15764,10 +15867,21 @@ function buildPrSweepScript() {
15764
15867
  "# - review:awaiting-human label \u2192 reason=awaiting-human",
15765
15868
  "#",
15766
15869
  "# Needs-worker-mode skip rules (PR carries review:needs-worker but is",
15767
- "# not eligible to drain this cycle):",
15768
- "# - review:fixing label present \u2192 reason=in-flight",
15769
- "# (a delegation is already being consumed; the marker plus the",
15770
- "# lease means another worker is mid-run \u2014 skip to stay idempotent)",
15870
+ "# not eligible to drain this cycle). A PR with NO review:fixing lease",
15871
+ "# always drains \u2014 an idle marker means no worker has been dispatched.",
15872
+ "# A PR that ALSO carries review:fixing drains UNLESS a worker is",
15873
+ "# demonstrably active, judged by the SAME signals the lease-reconcile",
15874
+ "# uses (fix-list / worker-report / branch-HEAD timestamps):",
15875
+ "# - no reviewer fix-list comment \u2192 reason=no-fixlist",
15876
+ "# (nothing to drain \u2014 e.g. a hand-applied lease; be conservative)",
15877
+ "# - worker-report newer than the fix-list \u2192 reason=reported",
15878
+ "# (a worker result already landed \u2014 the reviewer confirm pass owns it)",
15879
+ "# - branch HEAD newer than the fix-list \u2192 reason=in-flight",
15880
+ "# (a worker pushed \u2014 actively fixing / consumed-uncleared; the",
15881
+ "# lease-reconcile owns it)",
15882
+ "# - otherwise (delegated but idle, no worker has touched it) \u2192 DRAIN.",
15883
+ "# This is the deadlock fix: the FIRST worker of a delegation is",
15884
+ "# dispatched instead of being skipped forever on the bare lease.",
15771
15885
  "#",
15772
15886
  "# Usage:",
15773
15887
  "# .claude/procedures/pr-sweep.sh [review|needs-worker]",
@@ -15803,6 +15917,8 @@ function buildPrSweepScript() {
15803
15917
  " esac",
15804
15918
  "}",
15805
15919
  "",
15920
+ ...renderDelegationActiveSignalsHelper(),
15921
+ "",
15806
15922
  "# \u2500\u2500 modes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
15807
15923
  "",
15808
15924
  "cmd_review() {",
@@ -15837,15 +15953,22 @@ function buildPrSweepScript() {
15837
15953
  "",
15838
15954
  "cmd_needs_worker() {",
15839
15955
  " # Phase B1 feedback-drain: enumerate open PRs carrying the durable",
15840
- " # `review:needs-worker` marker that are NOT already in-flight (no",
15841
- " # `review:fixing` lease), ordered highest-priority-first so the",
15842
- " # orchestrator dispatches `issue-worker` per PR to consume the",
15843
- " # reviewer's fix-list. A PR holding both labels is a delegation",
15844
- " # mid-flight \u2014 it is skipped (reason=in-flight) so the drain stays",
15845
- " # idempotent and respects the cross-session lease semantics.",
15956
+ " # `review:needs-worker` marker and dispatch `issue-worker` per PR to",
15957
+ " # consume the reviewer's fix-list, ordered highest-priority-first.",
15958
+ " #",
15959
+ " # A PR without the `review:fixing` lease is an idle marker \u2014 no worker",
15960
+ " # has been dispatched \u2014 so it DRAINs directly. A PR that ALSO carries",
15961
+ " # `review:fixing` is a delegation; it DRAINs UNLESS a worker is",
15962
+ " # demonstrably active, judged by the shared `delegation_active_signals`",
15963
+ " # helper (the SAME signals cmd_lease_reconcile uses). Crucially, the",
15964
+ " # bare presence of `review:fixing` no longer skips: the reviewer",
15965
+ " # acquires the lease and applies the marker in ONE atomic pass and",
15966
+ " # then holds the lease across sessions, so a lease with no worker",
15967
+ " # activity yet is a delegated-but-idle PR whose FIRST worker must be",
15968
+ " # dispatched \u2014 skipping it on the bare lease wedged the PR forever.",
15846
15969
  " local prs",
15847
15970
  " prs=$(gh pr list --state open --label 'review:needs-worker' \\",
15848
- ' --json number,labels --limit 50 2>/dev/null || echo "[]")',
15971
+ ' --json number,labels,headRefOid --limit 50 2>/dev/null || echo "[]")',
15849
15972
  "",
15850
15973
  " local count",
15851
15974
  ` count=$(echo "$prs" | jq 'length')`,
@@ -15854,34 +15977,65 @@ function buildPrSweepScript() {
15854
15977
  " return 0",
15855
15978
  " fi",
15856
15979
  "",
15857
- " # First pass: split into skip (in-flight) and drain-candidate sets.",
15858
- " # The skip lines are emitted immediately for log visibility; the",
15859
- " # candidates are resolved to a priority key and sorted before",
15860
- " # emission so the orchestrator drains highest-priority first.",
15980
+ " # First pass: classify each PR. A PR with no `review:fixing` lease is",
15981
+ " # an idle marker \u2192 DRAIN directly (head-oid unused). A PR that also",
15982
+ " # carries `review:fixing` is a CANDIDATE \u2014 the demonstrably-active",
15983
+ " # predicate is applied per-PR in the loop below (it needs comment /",
15984
+ " # commit reads, so it cannot live in jq). Emit `kind\\tnumber\\thead`.",
15861
15985
  " local candidates",
15862
15986
  ` candidates=$(echo "$prs" | jq -r '`,
15863
15987
  " sort_by(.number) |",
15864
15988
  " .[] |",
15865
15989
  " (.labels | map(.name)) as $labels |",
15866
15990
  ' if ($labels | index("review:fixing")) then',
15867
- ' "SKIP\\t\\(.number)"',
15991
+ ' "CANDIDATE\\t\\(.number)\\t\\(.headRefOid)"',
15868
15992
  " else",
15869
- ' "DRAIN\\t\\(.number)"',
15993
+ ' "DRAIN\\t\\(.number)\\t"',
15870
15994
  " end",
15871
15995
  " ')",
15872
15996
  "",
15873
15997
  ' local drain_rows=""',
15874
15998
  ' local emitted_any=""',
15875
- " while IFS=$'\\t' read -r kind pr_num; do",
15999
+ " while IFS=$'\\t' read -r kind pr_num head_oid; do",
15876
16000
  ' [[ -z "$pr_num" ]] && continue',
15877
- ' if [[ "$kind" == "SKIP" ]]; then',
15878
- ' echo "PR_SKIP #${pr_num} reason=in-flight"',
15879
- ' emitted_any="1"',
15880
- " continue",
16001
+ "",
16002
+ ' if [[ "$kind" == "CANDIDATE" ]]; then',
16003
+ " # `review:fixing` present \u2014 drain UNLESS a worker is demonstrably",
16004
+ " # active. Mirror cmd_lease_reconcile's skip conditions exactly so",
16005
+ " # the drain and the reconcile never diverge on what 'active' means.",
16006
+ " local signals fixlist_at report_at head_at",
16007
+ ' signals=$(delegation_active_signals "$pr_num" "$head_oid")',
16008
+ ` IFS=$'\\x1f' read -r fixlist_at report_at head_at <<< "$signals"`,
16009
+ "",
16010
+ ' if [[ -z "$fixlist_at" ]]; then',
16011
+ " # No reviewer fix-list (e.g. a hand-applied lease). Nothing to",
16012
+ " # drain \u2014 be conservative and leave it for a human / reconcile.",
16013
+ ' echo "PR_SKIP #${pr_num} reason=no-fixlist"',
16014
+ ' emitted_any="1"',
16015
+ " continue",
16016
+ " fi",
16017
+ ' if [[ -n "$report_at" && "$report_at" > "$fixlist_at" ]]; then',
16018
+ " # A worker result already landed \u2014 the reviewer confirm pass",
16019
+ " # owns this PR now, not the drain.",
16020
+ ' echo "PR_SKIP #${pr_num} reason=reported"',
16021
+ ' emitted_any="1"',
16022
+ " continue",
16023
+ " fi",
16024
+ ' if [[ -n "$head_at" && "$head_at" > "$fixlist_at" ]]; then',
16025
+ " # Branch advanced past the fix-list \u2192 a worker pushed. Actively",
16026
+ " # fixing (or consumed-uncleared); the lease-reconcile owns it.",
16027
+ ' echo "PR_SKIP #${pr_num} reason=in-flight"',
16028
+ ' emitted_any="1"',
16029
+ " continue",
16030
+ " fi",
16031
+ " # else: delegated but idle \u2014 no worker has touched it. Fall",
16032
+ " # through to DRAIN. THIS is the deadlock fix.",
15881
16033
  " fi",
15882
- " # DRAIN candidate \u2014 resolve the linked issue's priority so the",
15883
- " # drain order is highest-priority-first, tie-broken by PR number",
15884
- " # ascending (FIFO, mirroring the queue scan's number-asc tie-break).",
16034
+ "",
16035
+ " # DRAIN (idle marker, or a delegated-but-idle candidate that fell",
16036
+ " # through) \u2014 resolve the linked issue's priority so the drain order",
16037
+ " # is highest-priority-first, tie-broken by PR number ascending",
16038
+ " # (FIFO, mirroring the queue scan's number-asc tie-break).",
15885
16039
  " local pkey",
15886
16040
  ' pkey=$(priority_key_for_pr "$pr_num")',
15887
16041
  ' drain_rows="${drain_rows}${pkey}\\t${pr_num}\\n"',
@@ -15919,7 +16073,7 @@ function buildPrSweepScript() {
15919
16073
  function buildPrSweepProcedure() {
15920
16074
  return {
15921
16075
  name: "pr-sweep.sh",
15922
- description: "Token-efficient PR enumeration for the orchestrator. In `review` mode (default) it emits one `PR_ELIGIBLE #<n>` or `PR_SKIP #<n> reason=<draft|human-required|awaiting-human>` line per open PR so the orchestrator dispatches `pr-reviewer` per eligible PR. In `needs-worker` mode it lists open PRs carrying the `review:needs-worker` marker that are not already in-flight (skipping any holding the `review:fixing` lease as `reason=in-flight`), emitting `PR_DRAIN #<n>` lines ordered highest-priority-first so the orchestrator drains the feedback queue by dispatching `issue-worker` per PR; `NO_NEEDS_WORKER_PRS` when none.",
16076
+ description: "Token-efficient PR enumeration for the orchestrator. In `review` mode (default) it emits one `PR_ELIGIBLE #<n>` or `PR_SKIP #<n> reason=<draft|human-required|awaiting-human>` line per open PR so the orchestrator dispatches `pr-reviewer` per eligible PR. In `needs-worker` mode it lists open PRs carrying the `review:needs-worker` marker and drains them, emitting `PR_DRAIN #<n>` lines ordered highest-priority-first so the orchestrator dispatches `issue-worker` per PR. A PR without the `review:fixing` lease is an idle marker and always drains; a PR that also holds `review:fixing` drains UNLESS a worker is demonstrably active (judged by the shared `delegation_active_signals` helper cmd_lease_reconcile also uses) \u2014 skipping as `reason=no-fixlist` / `reason=reported` / `reason=in-flight`. `NO_NEEDS_WORKER_PRS` when none.",
15923
16077
  content: buildPrSweepScript()
15924
16078
  };
15925
16079
  }
@@ -16200,19 +16354,27 @@ var orchestratorSubAgent = {
16200
16354
  "",
16201
16355
  "```",
16202
16356
  "PR_DRAIN #<n>",
16203
- "PR_SKIP #<n> reason=in-flight",
16357
+ "PR_SKIP #<n> reason=<no-fixlist|reported|in-flight>",
16204
16358
  "```",
16205
16359
  "",
16206
16360
  "The procedure enumerates every open PR carrying the",
16207
16361
  "`review:needs-worker` marker, ordered **highest-priority-first**",
16208
- "(linked-issue `priority:*`, tie-broken by PR number ascending). It",
16209
- "skips \u2014 never drains \u2014 any PR that **also** carries the",
16210
- "`review:fixing` lease: that label pair means a delegation is already",
16211
- "in flight (a worker is mid-run or the reviewer's confirm pass is",
16212
- "pending), so re-dispatching would double-work the fix-list. This is",
16213
- "what makes the phase **idempotent** \u2014 a PR already being worked, or",
16214
- "one whose marker was already cleared by a finished worker, never",
16215
- "appears as a `PR_DRAIN` line.",
16362
+ "(linked-issue `priority:*`, tie-broken by PR number ascending). A PR",
16363
+ "with an idle marker (no `review:fixing` lease) always drains. A PR",
16364
+ "that **also** carries the `review:fixing` lease still drains **unless",
16365
+ "a worker is demonstrably active** \u2014 the reviewer acquires the lease",
16366
+ "and applies the marker in one atomic pass and then holds the lease",
16367
+ "across sessions, so the bare lease does **not** mean a worker is",
16368
+ "running. The procedure judges 'active' with the same",
16369
+ "`delegation_active_signals` helper the maintenance lease-reconcile",
16370
+ "uses, skipping only when: there is no reviewer fix-list comment",
16371
+ "(`reason=no-fixlist`), a worker-report already landed newer than the",
16372
+ "fix-list (`reason=reported`), or the branch HEAD advanced past the",
16373
+ "fix-list (`reason=in-flight`). A delegated-but-idle PR \u2014 lease held",
16374
+ "but no worker has touched it \u2014 **drains**, so its FIRST worker gets",
16375
+ "dispatched instead of the PR wedging forever. The phase stays",
16376
+ "**idempotent**: a PR actively being worked, or one whose worker",
16377
+ "already reported, never appears as a `PR_DRAIN` line.",
16216
16378
  "",
16217
16379
  "If the script emits `NO_NEEDS_WORKER_PRS`, log the empty result and",
16218
16380
  "skip directly to Phase C.",
@@ -16564,9 +16726,12 @@ var orchestratorSubAgent = {
16564
16726
  " PR (highest-priority first) to consume the reviewer's fix-list,",
16565
16727
  " and loops until the queue is empty \u2014 feedback PRs are preferred",
16566
16728
  " over fresh issues, so Phase B1 runs before Phase E / Phase G.",
16567
- " Skip any PR that also carries the `review:fixing` lease (a",
16568
- " delegation already in flight) so the drain stays idempotent. A",
16569
- " single worker's failure never stops the drain; the next cycle",
16729
+ " A PR that also carries the `review:fixing` lease still drains",
16730
+ " **unless a worker is demonstrably active** (the procedure skips",
16731
+ " it as `reason=no-fixlist` / `reason=reported` / `reason=in-flight`",
16732
+ " via the shared active-signals check) \u2014 a delegated-but-idle PR",
16733
+ " holding the bare lease drains so its first worker is dispatched.",
16734
+ " A single worker's failure never stops the drain; the next cycle",
16570
16735
  " re-dispatches against any PR whose marker is still unconsumed."
16571
16736
  ].join("\n")
16572
16737
  };
@@ -17269,8 +17434,8 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
17269
17434
  "",
17270
17435
  "- The orchestrator runs **one full end-to-end cycle every invocation**: startup \u2192 PR review sweep \u2192 feedback drain \u2192 triage / unblock \u2192 maintenance \u2192 queue scan \u2192 delegate to `issue-worker` \u2192 cleanup",
17271
17436
  "- The orchestrator **never** implements code, creates branches, or pushes commits \u2014 it routes work to other sub-agents. The merge decision is still owned by the `pr-reviewer` sub-agent; Phase B only chooses which open PRs the reviewer should look at and dispatches one reviewer session per eligible PR (skipping drafts and any PR carrying `review:human-required` or `review:awaiting-human`).",
17272
- "- **Phase B produces `review:needs-worker` markers; Phase B1 consumes them.** Phase B's `pr-reviewer` dispatch leaves a durable `review:needs-worker` marker whenever it delegates a fix-list (it cannot spawn the worker in session \u2014 it may run at depth-1). Phase B1 (feedback drain) actively drains that queue every cycle: it dispatches a top-level `issue-worker` per open `review:needs-worker` PR \u2014 highest-priority first, looping until the queue is empty \u2014 to consume the reviewer's fix-list. **Feedback PRs are preferred over fresh issues**, so Phase B1 runs after the review sweep but before the queue scan (Phase E) and fresh delegation (Phase G). A PR that also carries the `review:fixing` lease is a delegation already in flight and is skipped, keeping the drain idempotent.",
17273
- "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency. PR-eligibility filtering for Phase B uses `.claude/procedures/pr-sweep.sh review`, which emits `PR_ELIGIBLE` / `PR_SKIP` lines; the Phase B1 feedback-drain enumeration uses `.claude/procedures/pr-sweep.sh needs-worker`, which emits `PR_DRAIN` / `PR_SKIP reason=in-flight` lines. The orchestrator never iterates raw `gh pr list` JSON.",
17437
+ "- **Phase B produces `review:needs-worker` markers; Phase B1 consumes them.** Phase B's `pr-reviewer` dispatch leaves a durable `review:needs-worker` marker whenever it delegates a fix-list (it cannot spawn the worker in session \u2014 it may run at depth-1). Phase B1 (feedback drain) actively drains that queue every cycle: it dispatches a top-level `issue-worker` per open `review:needs-worker` PR \u2014 highest-priority first, looping until the queue is empty \u2014 to consume the reviewer's fix-list. **Feedback PRs are preferred over fresh issues**, so Phase B1 runs after the review sweep but before the queue scan (Phase E) and fresh delegation (Phase G). A PR that also carries the `review:fixing` lease still drains **unless a worker is demonstrably active** \u2014 the reviewer acquires the lease and applies the marker in one atomic pass and holds the lease across sessions, so the bare lease does not mean a worker is running. The drain judges 'active' with the same `delegation_active_signals` helper the maintenance lease-reconcile uses: it skips only when there is no reviewer fix-list (`reason=no-fixlist`), a worker-report already landed (`reason=reported`), or the branch HEAD advanced past the fix-list (`reason=in-flight`); otherwise the delegated-but-idle PR drains so its FIRST worker gets dispatched.",
17438
+ "- All triage queries use `.claude/procedures/check-blocked.sh` for token efficiency. PR-eligibility filtering for Phase B uses `.claude/procedures/pr-sweep.sh review`, which emits `PR_ELIGIBLE` / `PR_SKIP` lines; the Phase B1 feedback-drain enumeration uses `.claude/procedures/pr-sweep.sh needs-worker`, which emits `PR_DRAIN` / `PR_SKIP reason=<no-fixlist|reported|in-flight>` lines. The orchestrator never iterates raw `gh pr list` JSON.",
17274
17439
  "- The queue scan reads only `priority:*` and `status:*` labels \u2014 type-routing (which typed agent handles a given `type:*` label) is the `issue-worker`'s concern, not the orchestrator's. The orchestrator's funnel-tier sort is a tie-breaker on `priority:*`, not a routing decision.",
17275
17440
  "- Priority order: critical > high > medium > low > trivial, then **funnel tier asc** (lower tier wins ties), then FIFO by issue number. Phase E's queue scan walks each priority bucket in turn (one `gh issue list` call per bucket, **capped at 50 issues per bucket**) and short-circuits on the first bucket whose survivors clear the `Depends on:` filter, so every higher-priority issue is visible even when the global ready backlog is much larger than 50.",
17276
17441
  "- Stale thresholds: 72h for in-progress, 168h for blocked",
@@ -22010,11 +22175,47 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
22010
22175
  "",
22011
22176
  " ## Traceability",
22012
22177
  " - **Source regulation:** <citation>",
22013
- " - **Requirements created:** #<N>, #<N> (SEC req:scan issues)",
22014
22178
  " - **Affected segments:** <relative links>",
22015
22179
  " - **Standards implementing:** <relative links> (if applicable)",
22180
+ "",
22181
+ " **Requirements created** (one per line, ascending issue-number",
22182
+ " order \u2014 append your own entries only):",
22183
+ "",
22184
+ " - #<N> \u2014 <PREFIX> req:scan",
22016
22185
  " ```",
22017
22186
  "",
22187
+ " The **Requirements created** block is an **append-only,",
22188
+ " one-issue-per-line, deterministically sorted** list \u2014 it is a",
22189
+ " merge-conflict hotspot because sibling `regulatory:impact`",
22190
+ " sessions land requirement entries into the same regulation page",
22191
+ " concurrently. Maintain it under the following discipline (the",
22192
+ " same discipline the `shared-editing-safety` rule prescribes for",
22193
+ " shared registries, applied here to this one list section \u2014 the",
22194
+ " surrounding regulation page is a hand-authored document, not a",
22195
+ " registry, so only this section is governed):",
22196
+ "",
22197
+ " - **One entry per line.** Write each requirement you created as",
22198
+ " its own new `- #<N> \u2014 <PREFIX> req:scan` bullet. Never collapse",
22199
+ " entries into a single comma-separated",
22200
+ " `**Requirements created:** #N, #M` line \u2014 that single mutable",
22201
+ " line is the original conflict cause and always conflicts when",
22202
+ " two sessions touch it.",
22203
+ " - **Ascending issue-number order.** Insert your bullet in the",
22204
+ " position that keeps the list sorted by issue number. A",
22205
+ " single-line bullet insert into a sorted list rarely truly",
22206
+ " conflicts, so git auto-merges concurrent sibling inserts.",
22207
+ " - **Append-only.** Never rewrite, reorder, or merge existing",
22208
+ " bullets. If a legacy comma line",
22209
+ " (`**Requirements created:** #N, #M`) already exists on the",
22210
+ " page, leave it untouched and add your entries as per-line",
22211
+ " bullets below it \u2014 do **not** rewrite the legacy line, because",
22212
+ " rewriting it is itself a conflict.",
22213
+ " - **Defer the commit of this section to the final pre-push",
22214
+ " step.** Pull the latest default branch before inserting, insert",
22215
+ " only your own entry, and if a concurrent sibling already landed",
22216
+ " a bullet, rebase and re-insert. Commit this shared list section",
22217
+ " on its own at the end, then push immediately.",
22218
+ "",
22018
22219
  "4. **Create `req:scan` issues** for each capability gap identified.",
22019
22220
  " Each issue hands off to the `requirements-analyst` bundle so its",
22020
22221
  " scan phase can deduplicate and open `req:write` issues in the",
@@ -22557,6 +22758,13 @@ var REQ_WRITE_ISSUE_SCHEMA_SECTION = [
22557
22758
  "",
22558
22759
  "### Strongly Recommended",
22559
22760
  "",
22761
+ "- **Requirement ID:** the reserved requirement ID (e.g.",
22762
+ " `FR-1240`), a HINT allocated at issue-creation. Optional \u2014",
22763
+ " legacy issues may omit it. The writer re-validates and finalizes",
22764
+ " the ID at write-start (via `next-requirement-id.sh`) before",
22765
+ " authoring, so this field is advisory: if a concurrent claim took",
22766
+ " the reserved number, the writer authors under the freshly",
22767
+ " finalized ID instead and updates this field to match.",
22560
22768
  "- **Inputs / Read:** bullet list of source files the writer should",
22561
22769
  " consult \u2014 the upstream proposal file (when one exists), the",
22562
22770
  " BCM/source document(s) the proposal cites, and any related",
@@ -22581,518 +22789,6 @@ var REQ_WRITE_ISSUE_SCHEMA_SECTION = [
22581
22789
  "the label to `status:ready` before the writer picks it up."
22582
22790
  ];
22583
22791
 
22584
- // src/agent/bundles/requirements-analyst.ts
22585
- function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
22586
- return {
22587
- name: "requirements-analyst",
22588
- description: "Discovers requirement gaps from BCM model docs, competitive analysis, product docs, and meeting extracts. Produces scan reports, proposals, and req:write issues for the downstream requirements-writer agent. Runs through a 2-phase pipeline (scan \u2192 draft-trace), one phase per session, tracked by req:* GitHub issue labels.",
22589
- model: AGENT_MODEL.POWERFUL,
22590
- maxTurns: 80,
22591
- platforms: { cursor: { exclude: true } },
22592
- prompt: [
22593
- "# Requirements Analyst Agent",
22594
- "",
22595
- "Dedicated agent loop for discovering requirement gaps from BCM (Business",
22596
- "Capability Model) documents, product docs, and competitive analysis \u2014 then",
22597
- "creating well-formed requirement issues for the downstream",
22598
- "`requirements-writer` agent to draft. Designed for scheduled execution",
22599
- "downstream of the BCM writer and company research agents.",
22600
- "",
22601
- "Follow your project's shared agent conventions (`AGENTS.md`,",
22602
- "`CLAUDE.md`, or equivalent) for all commit, branch, and PR rules.",
22603
- "",
22604
- "---",
22605
- "",
22606
- ...PROJECT_CONTEXT_MAINTAINER_SECTION,
22607
- "## Design Principles",
22608
- "",
22609
- "1. **Discover, don't write.** This agent identifies *what requirements are",
22610
- " missing*. The `requirements-writer` agent writes the actual documents.",
22611
- " The boundary keeps this agent fast and the `requirements-writer`",
22612
- " authoritative.",
22613
- "2. **Trace everything.** Every discovered gap links to the source that",
22614
- " revealed it (a BCM model doc, competitive analysis, product doc, or",
22615
- " meeting extract).",
22616
- "3. **Respect the taxonomy.** Route every discovered requirement to the",
22617
- " correct BCM category (FR, BR, NFR, SEC, DR, INT, OPS, UX, MT, ADR, TR)",
22618
- " using the shared disambiguation rules \u2014 the same taxonomy the",
22619
- " `requirements-writer` and `requirements-reviewer` load. The",
22620
- " canonical source lives in `requirements-taxonomy.ts` and is",
22621
- " embedded verbatim in both downstream agents' prompts.",
22622
- "4. **Deduplicate.** Before creating an issue, check whether a requirement",
22623
- " already exists or an issue is already open for it.",
22624
- "",
22625
- "---",
22626
- "",
22627
- "## State Machine Overview",
22628
- "",
22629
- "Requirements synthesis flows through **2 phases**:",
22630
- "",
22631
- "```",
22632
- "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
22633
- "\u2502 1. SCAN \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 2. DRAFT-TRACE \u2502",
22634
- "\u2502 Read docs, \u2502 \u2502 Write proposals,\u2502",
22635
- "\u2502 identify \u2502 \u2502 create req:write\u2502",
22636
- "\u2502 gaps, check \u2502 \u2502 issues, update \u2502",
22637
- "\u2502 for dupes \u2502 \u2502 source docs \u2502",
22638
- "\u2502 \u2502 \u2502 with traceability\u2502",
22639
- "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
22640
- "```",
22641
- "",
22642
- "This pipeline matches the `scan \u2192 draft-trace \u2192 write` pattern",
22643
- "used by the sibling openhi project. Draft and Trace were previously",
22644
- "separate phases; they were collapsed because the proposals file Draft",
22645
- "wrote was only ever consumed by Trace (no human review, no async work",
22646
- "between the two), so the phase boundary added latency without value.",
22647
- "See ADR-009 for the full reasoning.",
22648
- "",
22649
- "**Issue labels encode the phase:**",
22650
- "",
22651
- "| Label | Phase | Session work |",
22652
- "|-------|-------|-------------|",
22653
- "| `req:scan` | 1. Scan | Read source docs, identify potential requirement gaps, check against existing requirements and open issues, write deduplicated scan report |",
22654
- "| `req:draft-trace` | 2. Draft & Trace | Write proposals, create `req:write` GitHub issues for each proposal, and update source documents with traceability notes \u2014 all in a single session |",
22655
- "",
22656
- "All issues also carry `type:requirement` and a `status:*` label.",
22657
- "",
22658
- "**Issue count per scan cycle:** 1 scan + 1 draft-trace = **2 sessions**.",
22659
- "",
22660
- "**Shortened paths:**",
22661
- "- No gaps found after scan \u2192 skip draft-trace \u2192 **1 session**",
22662
- "",
22663
- "---",
22664
- "",
22665
- "## Configurable Paths",
22666
- "",
22667
- "Projects adopting this bundle must define these paths in their agent",
22668
- "configuration (`agentConfig.rules` extension or project-level docs):",
22669
- "",
22670
- "| Placeholder | Meaning | Typical value |",
22671
- "|-------------|---------|---------------|",
22672
- `| \`<BCM_DOCS_ROOT>\` | Root of BCM model docs (capability models) | \`${paths.bcmRoot}/\` |`,
22673
- `| \`<COMPETITIVE_ROOT>\` | Competitive analysis docs | \`${paths.docsRoot}/business-strategy/competitive/\` |`,
22674
- `| \`<PRODUCT_ROOT>\` | Product roadmap / entity taxonomy | \`${paths.docsRoot}/product/\` |`,
22675
- `| \`<MEETINGS_ROOT>\` | Meeting extracts | \`${paths.docsRoot}/research/meetings/\` |`,
22676
- `| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Scan reports and proposals | \`${paths.researchRequirementsRoot}/\` |`,
22677
- `| \`<REQUIREMENTS_ROOT>\` | Final requirement documents (owned by requirements-writer) | \`${paths.requirementsRoot}/\` |`,
22678
- "| `<PREFIX>` | Project-specific requirement ID prefix | e.g. `VRTX`, `ACME` |",
22679
- "",
22680
- "If your project stores these in different locations, substitute accordingly",
22681
- "wherever the phase instructions reference a path.",
22682
- "",
22683
- "---",
22684
- "",
22685
- "## Agent Loop",
22686
- "",
22687
- "Run this loop exactly once per session. Never start a second issue.",
22688
- "",
22689
- "1. Claim one open `type:requirement` issue using phase priority:",
22690
- " `req:scan` > `req:draft-trace`.",
22691
- "2. Transition `status:ready` \u2192 `status:in-progress` and create the branch",
22692
- " per your project's branch-naming convention.",
22693
- "3. Execute the phase handler that matches the issue's `req:*` label.",
22694
- "4. Commit, push, open a PR (if applicable), and close the issue per your",
22695
- " project's PR workflow.",
22696
- "",
22697
- "---",
22698
- "",
22699
- "## Phase 1: Scan (`req:scan`)",
22700
- "",
22701
- "**Goal:** Read source documents, identify where requirements are missing,",
22702
- "incomplete, or contradictory, then check each potential gap against existing",
22703
- "requirements and open issues to eliminate duplicates. Produces a single",
22704
- "deduplicated scan report.",
22705
- "",
22706
- "**Budget:** Reading source docs + reading requirement registries + searching",
22707
- "issues. Write one deduplicated scan output file.",
22708
- "",
22709
- "### Scan Sources",
22710
- "",
22711
- "The issue specifies which source(s) to scan. Common scan scopes:",
22712
- "",
22713
- "| Scope | What to read | What to look for |",
22714
- "|-------|-------------|-----------------|",
22715
- `| **BCM model doc** | One \`{PREFIX}-NNN\` doc under \`<BCM_DOCS_ROOT>\` | The doc's project-relevance section (commonly \`## <Project> Relevance\` or \`## Strategic Implications\`) \u2014 gaps where capabilities exist but no FR/BR/INT addresses them. Use \`${paths.docsRoot}/project-context.md\` to judge what is relevant. |`,
22716
- "| **Competitive analysis** | One `comp-*.md` doc under `<COMPETITIVE_ROOT>` | Feature comparison gaps \u2014 competitor features the product lacks requirements for |",
22717
- "| **Product roadmap** | `<PRODUCT_ROOT>/prioritized-feature-roadmap.md` | Roadmap items without corresponding FRs |",
22718
- "| **Entity taxonomy** | `<PRODUCT_ROOT>/entity-taxonomy.md` | Entities without CRUD requirements (FR), data requirements (DR), or security requirements (SEC) |",
22719
- "| **Meeting extract** | `<MEETINGS_ROOT>/meeting-*.extract.md` | Requirements identified but not yet formalized |",
22720
- "",
22721
- "### Steps",
22722
- "",
22723
- "1. **Read the source documents** specified in the issue.",
22724
- "",
22725
- "2. **Identify potential gaps.** For each potential missing requirement:",
22726
- " - Classify into the correct BCM category (FR, BR, NFR, SEC, DR, INT,",
22727
- " OPS, UX, MT, ADR, TR)",
22728
- " - Apply the shared disambiguation rules (from `requirements-taxonomy.ts`,",
22729
- " also embedded in the requirements-writer and requirements-reviewer",
22730
- " prompts)",
22731
- " - Note the source that revealed the gap",
22732
- " - Estimate priority based on the source context",
22733
- "",
22734
- "3. **Read the requirements registry.** Scan `_index.md` files in each",
22735
- " `<REQUIREMENTS_ROOT>/<category>/` directory to know what already exists.",
22736
- "",
22737
- "4. **Search for existing issues.** For each potential gap, search open issues:",
22738
- " ```bash",
22739
- ' gh issue list --label "type:requirement" --state open \\',
22740
- " --json number,title --limit 100",
22741
- " ```",
22742
- "",
22743
- "5. **Classify each gap:**",
22744
- " - **New** \u2014 no existing requirement or open issue covers this",
22745
- " - **Duplicate** \u2014 an existing requirement already addresses this",
22746
- " - **In progress** \u2014 an open issue already targets this",
22747
- " - **Partial** \u2014 existing requirement partially covers this; note the gap",
22748
- "",
22749
- "6. **Write the deduplicated scan report** to:",
22750
- " ```",
22751
- " <RESEARCH_REQUIREMENTS_ROOT>/req-scan-<scope>-<YYYY-MM-DD>.md",
22752
- " ```",
22753
- "",
22754
- " Format:",
22755
- " ```markdown",
22756
- " ---",
22757
- ' title: "Requirements Scan: <scope>"',
22758
- " date: YYYY-MM-DD",
22759
- " parent_issue: <N>",
22760
- " status: complete",
22761
- " ---",
22762
- "",
22763
- " ## Source Documents Reviewed",
22764
- " - <path> \u2014 <brief description>",
22765
- "",
22766
- " ## Existing Requirements Checked",
22767
- " - <category>: <count> existing docs, <count> open issues",
22768
- "",
22769
- " ## Identified Gaps (New)",
22770
- " ### Gap 1: <Title>",
22771
- " - **Category:** FR / BR / NFR / SEC / DR / INT / OPS / UX / MT / ADR / TR",
22772
- " - **Source:** <path to doc + section that revealed this gap>",
22773
- " - **Priority:** High / Normal / Low",
22774
- " - **Rationale:** <why this requirement is needed>",
22775
- " - **Duplicate check:** No existing requirement or open issue found",
22776
- " - **Proposed scope:** <1-2 sentences on what the requirement should cover>",
22777
- "",
22778
- " ## Already Covered",
22779
- " <list of potential gaps that turned out to already have requirements>",
22780
- "",
22781
- " ## In Progress",
22782
- " <gaps that already have open issues \u2014 include issue numbers>",
22783
- "",
22784
- " ## Ambiguous / Needs Human Decision",
22785
- " <gaps where the correct category or scope is unclear>",
22786
- " ```",
22787
- "",
22788
- "7. **Create downstream issues based on findings:**",
22789
- " - If any new gaps were identified \u2192 create `req:draft-trace` issue",
22790
- " (blocked on this issue via `Depends on: #N`).",
22791
- " - If **no gaps** were found \u2192 stop (no further phases needed). Comment",
22792
- " on the issue noting that no gaps were identified, and proceed directly",
22793
- " to commit and push. The scan issue will be marked done with no",
22794
- " downstream work needed.",
22795
- "",
22796
- "8. **Commit and push.**",
22797
- "",
22798
- "---",
22799
- "",
22800
- "## Phase 2: Draft & Trace (`req:draft-trace`)",
22801
- "",
22802
- "**Goal:** Expand each identified gap into a requirement proposal, create",
22803
- "a `req:write` GitHub issue for each proposal so the downstream",
22804
- "`requirements-writer` bundle picks it up, and backfill source-document",
22805
- "traceability notes \u2014 all in a single session.",
22806
- "",
22807
- "**Budget:** No web searches. Reading + writing proposals + issue creation",
22808
- "+ minor traceability edits to source documents.",
22809
- "",
22810
- "Draft and Trace were previously separate phases. They were collapsed",
22811
- "because the proposals file written by Draft was only ever consumed by",
22812
- "Trace \u2014 no human review, no async work, no CI validation sat between",
22813
- "them. See ADR-009 for the decision record.",
22814
- "",
22815
- "### `req:write` issue schema",
22816
- "",
22817
- "The `req:write` issues this phase creates are picked up by the",
22818
- "downstream `requirements-writer` agent, which parses a strict",
22819
- "schema on intake. The authoritative schema is defined in the",
22820
- "`requirements-writer` sub-agent prompt under **The `req:write`",
22821
- "Issue Schema** \u2014 every `req:write` issue this phase opens must",
22822
- "conform. The same schema is embedded in the",
22823
- "`requirements-reviewer` follow-up generator so the three",
22824
- "producers never drift apart.",
22825
- "",
22826
- ...REQ_WRITE_ISSUE_SCHEMA_SECTION,
22827
- "",
22828
- "**Deriving the three required fields from the proposal.** For",
22829
- "every `req:write` issue this phase opens, the three fields come",
22830
- "directly from the proposal entry written earlier in the same",
22831
- "session:",
22832
- "",
22833
- "- **Category** \u2014 the proposal's `**Category:**` line.",
22834
- "- **Tier** \u2014 the proposal's `**Tier:**` line.",
22835
- "- **Output Path** \u2014",
22836
- " `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`,",
22837
- " using the sequence number determined in the proposal step below.",
22838
- "",
22839
- "**Validation step \u2014 required before opening the issue.** Before",
22840
- "calling `gh issue create`, verify all three required fields are",
22841
- "populated and internally consistent (Category matches title",
22842
- "prefix, Tier matches `tier:*` label, Output Path filename starts",
22843
- "with the Category prefix). If any field cannot be derived, open",
22844
- "the issue with `status:needs-attention` (not `status:ready`) and",
22845
- "include a `Missing: <field> \u2014 <one-line reason>` line so a human",
22846
- "triaging the issue only has to supply the remaining value(s)",
22847
- "before flipping the label to `status:ready`.",
22848
- "",
22849
- "### Steps",
22850
- "",
22851
- "1. **Read the scan report** from Phase 1.",
22852
- "",
22853
- "2. **For each gap**, write a detailed proposal:",
22854
- "",
22855
- " ```markdown",
22856
- " ## Proposed: <PREFIX>-<NNN> \u2014 <Title>",
22857
- "",
22858
- " **Category:** <FR/BR/NFR/SEC/DR/INT/OPS/UX/MT/ADR/TR>",
22859
- " **Tier:** <platform/industry/customer-workflow/consumer-app>",
22860
- " **Priority:** <High/Normal/Low>",
22861
- " **Source:** <document path and section>",
22862
- "",
22863
- " ### Summary",
22864
- " <2-3 sentences describing what the requirement should capture>",
22865
- "",
22866
- " ### Draft Acceptance Criteria",
22867
- " - [ ] <testable criterion 1>",
22868
- " - [ ] <testable criterion 2>",
22869
- " - [ ] <testable criterion 3>",
22870
- "",
22871
- " ### Traceability",
22872
- " - **Implements:** <BR or parent requirement if applicable>",
22873
- " - **Related:** <existing requirements that interact with this one>",
22874
- " - **Source:** <BCM doc, competitive analysis, or meeting that revealed",
22875
- " the gap \u2014 use a markdown link. If the source is a meeting note, the",
22876
- " downstream requirement doc must include the same meeting as a link in",
22877
- " its Traceability `Related:` list.>",
22878
- "",
22879
- " ### Decision Authority",
22880
- ' <"Direct write" for BR/FR/NFR/SEC/UX, or "Proposed \u2014 needs human',
22881
- ' decision" for ADR/TR, or "Mixed \u2014 defer technology choices" for',
22882
- " DR/MT/INT/OPS>",
22883
- "",
22884
- " ### Notes for Requirements Writer",
22885
- " <any context the writer should know \u2014 related ADRs, existing partial",
22886
- " coverage, relevant competitive features>",
22887
- " ```",
22888
- "",
22889
- "3. **Write the proposals** to:",
22890
- " ```",
22891
- " <RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<YYYY-MM-DD>.md",
22892
- " ```",
22893
- "",
22894
- "4. **Determine next sequence numbers.** Check each target category",
22895
- " directory under `<REQUIREMENTS_ROOT>/<category>/` to find the next",
22896
- " available `NNN` for each proposed requirement.",
22897
- "",
22898
- "5. **Create requirement issues.** For each proposal, file a",
22899
- " `req:write` issue using the canonical recipe documented in",
22900
- " `## Template: req:write` of",
22901
- " `docs/src/content/docs/agents/issue-templates.md`.",
22902
- "",
22903
- ` All \`type:requirement\` issues default to \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\` (override`,
22904
- " only if the proposal's priority was explicitly High or Low). Each",
22905
- " issue must also carry the `req:write` phase label plus the matching",
22906
- " `tier:*` label so the downstream `requirements-writer` bundle picks",
22907
- " it up with the correct tier. Concretely, the label list includes",
22908
- ' `--label "type:requirement"`, `--label "req:write"`,',
22909
- ` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
22910
- ` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
22911
- "",
22912
- " The body must carry the three writer-required fields in an",
22913
- " `## Objective` block, written as bold-prefixed lines so the",
22914
- " writer's intake parser can pull them out:",
22915
- "",
22916
- " - `**Category:** <BR/FR/NFR/TR/ADR/SEC/DR/INT/OPS/UX/MT>`",
22917
- " - `**Tier:** <platform/industry/customer-workflow/consumer-app>`",
22918
- " - `**Output Path:** <REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`",
22919
- "",
22920
- " Then under `## Inputs / Read`, list the proposals file",
22921
- " (`<RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<date>.md`)",
22922
- " and any source documents the writer should consult (BCM model",
22923
- " docs, competitive analyses, product docs).",
22924
- "",
22925
- " When one of Category / Tier / Output Path could not be derived",
22926
- " from the proposal (for example, the proposal omitted the Tier",
22927
- ` line), replace \`status:${labelsForPhase(issueDefaults, "req:write").status}\` with \`status:needs-attention\` in`,
22928
- " the label list and add a `Missing: <field> \u2014 <reason>` line",
22929
- " directly below the Output Path line in the body. Populate",
22930
- " whichever of the three fields **could** be derived so a human",
22931
- " triaging the issue has the minimum possible cleanup.",
22932
- "",
22933
- "6. **Update source documents.** In each BCM model doc or competitive",
22934
- " analysis that was scanned, add a note in the project-relevance /",
22935
- " strategic-implications section (whichever heading the source doc uses)",
22936
- " indicating that a requirement issue was created:",
22937
- "",
22938
- " ```markdown",
22939
- " - Gap addressed: see [<PREFIX>-<NNN>](<relative path to requirement doc>)",
22940
- " (issue #<N>)",
22941
- " ```",
22942
- "",
22943
- "7. **Comment on the scan issue** with a summary of all issues created and",
22944
- " all docs updated.",
22945
- "",
22946
- "8. **Commit and push.**",
22947
- "",
22948
- "---",
22949
- "",
22950
- "## Coordination with Other Agents",
22951
- "",
22952
- "| Direction | Agent | What |",
22953
- "|-----------|-------|------|",
22954
- `| Upstream | BCM Writer | Scans capability-model docs for project-relevance gaps (judged against \`${paths.docsRoot}/project-context.md\`) |`,
22955
- "| Upstream | Company Research | Scans competitive analysis for feature comparison gaps |",
22956
- "| Upstream | Meeting Analyst | Scans meeting extracts for requirement proposals |",
22957
- "| Downstream | `requirements-writer` | Picks up the `type:requirement` + `req:write` issues this agent creates and drafts the actual requirement document |",
22958
- "",
22959
- "**File boundaries:** Writes to `<RESEARCH_REQUIREMENTS_ROOT>/req-*.md` and",
22960
- "minor traceability edits to `<BCM_DOCS_ROOT>` and `<COMPETITIVE_ROOT>`.",
22961
- "Never writes to `<REQUIREMENTS_ROOT>/` \u2014 that is owned by the",
22962
- "requirements-writer agent.",
22963
- "",
22964
- "---",
22965
- "",
22966
- "## Blocked Issues",
22967
- "",
22968
- "Additional block reasons specific to requirements synthesis:",
22969
- "- Source document has unresolved contradictions",
22970
- "- Category classification is ambiguous (needs human disambiguation)",
22971
- "- Dependent BCM documents are still in draft with placeholder content",
22972
- "",
22973
- "---",
22974
- "",
22975
- "## Rules",
22976
- "",
22977
- "- **Discover, don't write requirements.** Create issues for the",
22978
- " `requirements-writer` agent \u2014 don't write requirement documents",
22979
- " directly.",
22980
- "- **Deduplicate rigorously.** Check both existing docs and open issues",
22981
- " before flagging a gap.",
22982
- "- **Respect decision authority.** Mark ADR/TR proposals as needing human",
22983
- " decision. Don't create direct-write issues for technology choices.",
22984
- "- **Bidirectional traceability.** Every `req-scan-*.md` and",
22985
- " `req-proposals-*.md` must include a `## Produced` section listing the",
22986
- " downstream requirement issues (and eventual requirement documents) it",
22987
- " spawned, as markdown links; each formal requirement document under",
22988
- " `<REQUIREMENTS_ROOT>/` must include a forward link back to the scan or",
22989
- " proposal that produced it."
22990
- ].join("\n")
22991
- };
22992
- }
22993
- function buildScanRequirementsSkill(issueDefaults) {
22994
- return {
22995
- name: "scan-requirements",
22996
- description: "Kick off a requirements-analyst scan across BCM model docs, competitive analysis, product docs, or meeting extracts. Creates a req:scan issue and dispatches Phase 1.",
22997
- disableModelInvocation: true,
22998
- userInvocable: true,
22999
- context: "fork",
23000
- agent: "requirements-analyst",
23001
- platforms: { cursor: { exclude: true } },
23002
- instructions: [
23003
- "# Scan Requirements",
23004
- "",
23005
- "Kick off a requirements-analyst scan cycle. Creates a `req:scan` issue",
23006
- "targeted at the requested scope and dispatches Phase 1 (Scan) in the",
23007
- "requirements-analyst agent.",
23008
- "",
23009
- "## Usage",
23010
- "",
23011
- "/scan-requirements <scope>",
23012
- "",
23013
- "Where `<scope>` is one of:",
23014
- "- `bcm:<PREFIX-NNN>` \u2014 a single BCM model doc",
23015
- "- `competitive:<slug>` \u2014 a single competitive analysis doc",
23016
- "- `product-roadmap` \u2014 the prioritized feature roadmap",
23017
- "- `entity-taxonomy` \u2014 the entity taxonomy doc",
23018
- "- `meeting:<slug>` \u2014 a meeting extract",
23019
- "- `all-bcm` / `all-competitive` \u2014 full sweep (long-running)",
23020
- "",
23021
- "## Steps",
23022
- "",
23023
- `1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
23024
- ` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
23025
- "2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
23026
- "3. If gaps are found, a `req:draft-trace` issue is created automatically.",
23027
- "",
23028
- "## Output",
23029
- "",
23030
- "- A `req-scan-<scope>-<YYYY-MM-DD>.md` file under the project's research",
23031
- " requirements directory.",
23032
- "- A `req:draft-trace` issue if any gaps were identified."
23033
- ].join("\n")
23034
- };
23035
- }
23036
- function buildRequirementsAnalystBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
23037
- return {
23038
- name: "requirements-analyst",
23039
- description: "Requirements gap-discovery agent bundle for BCM-driven projects. 2-phase pipeline (scan, draft-trace) with req:* phase labels.",
23040
- appliesWhen: () => true,
23041
- rules: [
23042
- {
23043
- name: "requirements-analyst-workflow",
23044
- description: "Describes the 2-phase requirements gap-discovery pipeline, the req:* label taxonomy, and the boundary with the downstream requirements-writer agent.",
23045
- scope: AGENT_RULE_SCOPE.ALWAYS,
23046
- content: [
23047
- "# Requirements Analyst Workflow",
23048
- "",
23049
- "Use `/scan-requirements <scope>` to kick off a requirements gap",
23050
- "discovery cycle. The pipeline runs in 2 phases \u2014 scan and",
23051
- "draft-trace \u2014 each tracked by its own GitHub issue labeled",
23052
- "`req:scan` or `req:draft-trace`. All issues carry",
23053
- "`type:requirement`.",
23054
- "",
23055
- "The requirements-analyst *discovers gaps, drafts proposals, and",
23056
- "opens `req:write` issues for the downstream writer*; it does",
23057
- "**not** write final requirement documents. Writing is the job of",
23058
- "the downstream `requirements-writer` agent (a separate bundle).",
23059
- "Keep that boundary clean: proposals land under the research",
23060
- "requirements directory, not under the authoritative requirements",
23061
- "tree. The draft-trace phase tags new issues with `req:write` so",
23062
- "the writer bundle picks them up automatically.",
23063
- "",
23064
- "See the `requirements-analyst` agent definition for full workflow",
23065
- "details and phase-by-phase instructions."
23066
- ].join("\n"),
23067
- platforms: {
23068
- cursor: { exclude: true }
23069
- },
23070
- tags: ["workflow"]
23071
- }
23072
- ],
23073
- skills: [buildScanRequirementsSkill(issueDefaults)],
23074
- subAgents: [buildRequirementsAnalystSubAgent(paths, issueDefaults)],
23075
- labels: [
23076
- {
23077
- name: "type:requirement",
23078
- color: "1D76DB",
23079
- description: "Work that produces or discovers a requirement document (FR, BR, NFR, etc.)"
23080
- },
23081
- {
23082
- name: "req:scan",
23083
- color: "C5DEF5",
23084
- description: "Phase 1: scan source docs for requirement gaps and deduplicate"
23085
- },
23086
- {
23087
- name: "req:draft-trace",
23088
- color: "BFDADC",
23089
- description: "Phase 2: draft proposals, create req:write issues, and backfill source-doc traceability"
23090
- }
23091
- ]
23092
- };
23093
- }
23094
- var requirementsAnalystBundle = buildRequirementsAnalystBundle();
23095
-
23096
22792
  // src/agent/bundles/requirements-writer.ts
23097
22793
  var templateBr = (paths) => `---
23098
22794
  title: "BR-NNN: [Business Requirement Title]"
@@ -24453,7 +24149,7 @@ Use [\`_template-{PREFIX}.md\`](../templates/_template-{PREFIX}.md) for all new
24453
24149
  ## Conventions
24454
24150
 
24455
24151
  - File naming: \`{PREFIX}-NNN-descriptive-slug.md\`
24456
- - Number sequentially \u2014 check the table above for the next available number
24152
+ - Number sequentially \u2014 check the table above for the next available number. Do not embed per-requirement descriptions, a running change-log, or a mutable "next available: X (\u2026)" counter in this Conventions section; the table above is the sole registry, and each document's ID is finalized at write-start.
24457
24153
  - Every document must include YAML frontmatter with a \`title\` field
24458
24154
  - Every document must include a \`## Traceability\` section
24459
24155
  `;
@@ -24905,664 +24601,1455 @@ var REQUIREMENTS_WRITER_PATHS = {
24905
24601
  /** The standards-and-frameworks reference document. */
24906
24602
  standardsRef: `${WRITE_REQUIREMENT_REFERENCES_ROOT}/standards-and-frameworks.md`
24907
24603
  };
24908
- var WRITE_REQUIREMENT_EVALS_JSON = JSON.stringify(
24909
- {
24910
- skill_name: "write-requirement",
24911
- evals: [
24604
+ var WRITE_REQUIREMENT_EVALS_JSON = JSON.stringify(
24605
+ {
24606
+ skill_name: "write-requirement",
24607
+ evals: [
24608
+ {
24609
+ id: 1,
24610
+ prompt: "We had a kickoff meeting where the team decided we need self-service event registration for B2B conferences. The main pain point is that current solutions take months to configure \u2014 operators send spreadsheets and wait weeks for changes. We want organizers to set up registration themselves. Target market is small to mid-size B2B conferences with 500-5000 registrants. Write the BR and FR requirements for this.",
24611
+ expected_output: "A BR document in the configured business-requirements directory capturing the business need (self-service registration, reduced configuration time, target market) with stakeholders, success metrics, and scope. One or more FR documents in the functional-requirements directory describing the self-service configuration workflow with user stories, main/alternative/exception flows, and acceptance criteria. All documents follow the category template exactly, carry YAML frontmatter, ship as status Draft, and include traceability links between the BR and FRs. No technology choices are made \u2014 any tech implications are deferred to Proposed ADR or TR documents.",
24612
+ files: [],
24613
+ product_context_refs: [
24614
+ "Mission",
24615
+ "In-Scope Capabilities",
24616
+ "Out of Scope"
24617
+ ]
24618
+ },
24619
+ {
24620
+ id: 2,
24621
+ prompt: "We need to add payment processing to our platform. Registrants should be able to pay with credit cards and we need to support multiple registration tiers with different prices. Write the requirements for this.",
24622
+ expected_output: "FR documents covering payment processing (user-visible behavior: selecting a tier, entering payment, receiving confirmation) and registration tiers (configuring tiers, pricing, capacity). The FR documents do NOT select a specific payment provider \u2014 instead, a Proposed ADR is created listing payment provider options (e.g., Stripe, Braintree, Adyen) with pros/cons and a recommendation, with the Decision section set to 'Pending human review'. An INT document describes the integration shape (direction, error handling, SLA expectations) but defers provider selection to the ADR. Open items cross-reference the pending decision.",
24623
+ files: [],
24624
+ product_context_refs: ["Domain Vocabulary"]
24625
+ },
24626
+ {
24627
+ id: 3,
24628
+ prompt: "Here are notes from our meeting: 'We discussed monitoring. The team agreed we need 99.9% uptime SLA, p99 latency under 200ms for registration API calls, and real-time alerting when error rates spike above 1%. Alice suggested Datadog, Bob preferred CloudWatch since we're already on AWS. No decision was made on tooling.' Write the requirements.",
24629
+ expected_output: "An NFR document with measurable targets (99.9% uptime, p99 < 200ms, error rate alerting threshold at 1%) \u2014 written directly as Draft. An OPS document describing monitoring and alerting requirements (what needs monitoring, alerting rules, incident response) \u2014 written directly for the requirements portion. A Proposed ADR or TR for the monitoring tooling decision, listing Datadog and CloudWatch as options with pros/cons, noting each team member's preference, and recommending one with rationale. The ADR/TR has status Proposed with an open item flagging human decision required. The OPS document references the pending tooling decision in its open items.",
24630
+ files: [],
24631
+ product_context_refs: ["Domain Vocabulary"]
24632
+ }
24633
+ ]
24634
+ },
24635
+ null,
24636
+ 2
24637
+ );
24638
+ function buildRequirementsWriterReferenceFiles(paths) {
24639
+ return [
24640
+ {
24641
+ path: "_references/templates/_template-BR.md",
24642
+ content: templateBr(paths)
24643
+ },
24644
+ {
24645
+ path: "_references/templates/_template-FR.md",
24646
+ content: templateFr(paths)
24647
+ },
24648
+ {
24649
+ path: "_references/templates/_template-NFR.md",
24650
+ content: templateNfr(paths)
24651
+ },
24652
+ {
24653
+ path: "_references/templates/_template-TR.md",
24654
+ content: templateTr(paths)
24655
+ },
24656
+ {
24657
+ path: "_references/templates/_template-ADR.md",
24658
+ content: templateAdr(paths)
24659
+ },
24660
+ {
24661
+ path: "_references/templates/_template-SEC.md",
24662
+ content: templateSec(paths)
24663
+ },
24664
+ {
24665
+ path: "_references/templates/_template-DR.md",
24666
+ content: templateDr(paths)
24667
+ },
24668
+ {
24669
+ path: "_references/templates/_template-INT.md",
24670
+ content: templateInt(paths)
24671
+ },
24672
+ {
24673
+ path: "_references/templates/_template-OPS.md",
24674
+ content: templateOps(paths)
24675
+ },
24676
+ {
24677
+ path: "_references/templates/_template-UX.md",
24678
+ content: templateUx(paths)
24679
+ },
24680
+ {
24681
+ path: "_references/templates/_template-MT.md",
24682
+ content: templateMt(paths)
24683
+ },
24684
+ {
24685
+ path: "_references/templates/_template-category-README.md",
24686
+ content: TEMPLATE_CATEGORY_README
24687
+ },
24688
+ {
24689
+ path: "_references/templates/_template-requirements-README.md",
24690
+ content: templateRequirementsReadme(paths)
24691
+ },
24692
+ {
24693
+ path: "_references/standards-and-frameworks.md",
24694
+ content: STANDARDS_AND_FRAMEWORKS
24695
+ },
24696
+ {
24697
+ path: "evals/evals.json",
24698
+ content: WRITE_REQUIREMENT_EVALS_JSON
24699
+ }
24700
+ ];
24701
+ }
24702
+ function buildRequirementsWriterSubAgent(paths) {
24703
+ return {
24704
+ name: "requirements-writer",
24705
+ description: "Writes formal requirement documents (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) from upstream proposals using the 11-category taxonomy, the four-tier classification, and the decision-authority rules (direct-write vs. propose-only ADR/TR). Handles one req:write issue per session. Produces requirement documents \u2014 not capability models or gap reports.",
24706
+ model: AGENT_MODEL.POWERFUL,
24707
+ maxTurns: 80,
24708
+ platforms: { cursor: { exclude: true } },
24709
+ prompt: [
24710
+ "# Requirements Writer Agent",
24711
+ "",
24712
+ "You author formal requirement documents using the 11-category",
24713
+ "taxonomy (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) and the",
24714
+ "four-tier architectural classification (Platform, Industry, Customer",
24715
+ "Workflow, Consumer Application). Each session handles exactly **one**",
24716
+ "`req:write` issue and writes exactly **one** requirement document.",
24717
+ "",
24718
+ "This agent produces **requirement documents only** \u2014 capability",
24719
+ "models are written by the `bcm-writer` agent and requirement-gap",
24720
+ "discovery is the responsibility of the `requirements-analyst` agent.",
24721
+ "Keep this boundary clean: never open `req:scan`,",
24722
+ "`req:draft-trace`, or `bcm:*` issues from this pipeline.",
24723
+ "",
24724
+ "Follow your project's shared agent conventions (`AGENTS.md`,",
24725
+ "`CLAUDE.md`, or equivalent) for all commit, branch, and PR rules.",
24726
+ "",
24727
+ "---",
24728
+ "",
24729
+ ...PROJECT_CONTEXT_READER_SECTION,
24730
+ "## Design Principles",
24731
+ "",
24732
+ "1. **One requirement per session.** Each `req:write` issue maps to a",
24733
+ " single requirement document. Never write two documents in one",
24734
+ " session and never start a second issue.",
24735
+ "2. **Templates are authoritative.** Every category has a template",
24736
+ " under `<TEMPLATES_ROOT>`. Use it verbatim \u2014 do not invent new",
24737
+ " sections or reorder existing ones. A section that does not apply",
24738
+ " gets `Not applicable \u2014 <reason>` rather than being omitted.",
24739
+ "3. **Decision authority is non-negotiable.** Direct-write categories",
24740
+ " ship as `Status: Draft`. ADR and TR documents ship as",
24741
+ " `Status: Proposed` with a Recommendation that frames a human",
24742
+ " decision \u2014 never decide for the human.",
24743
+ "4. **Trace upstream.** Every requirement links back to the proposal",
24744
+ " that produced it, the source document(s) cited in that proposal,",
24745
+ " and the upstream BCM capability or business need it serves.",
24746
+ "5. **Cite, don't invent.** When the proposal does not supply a",
24747
+ " stakeholder, metric, threat model entry, or technology option,",
24748
+ " write `TODO:` and flag the issue with `status:needs-attention`",
24749
+ " rather than fabricating content.",
24750
+ "",
24751
+ "---",
24752
+ "",
24753
+ "## Configurable Paths",
24754
+ "",
24755
+ "The pipeline uses these placeholders. Consuming projects override the",
24756
+ "defaults by passing paths in the `/write-requirement` skill",
24757
+ `invocation, by recording overrides in \`${paths.docsRoot}/project-context.md\`, or`,
24758
+ "by extending this rule in their own `agentConfig.rules`.",
24759
+ "",
24760
+ "| Placeholder | Meaning | Default |",
24761
+ "|-------------|---------|---------|",
24762
+ `| \`<REQUIREMENTS_ROOT>\` | Root folder for final requirement documents | \`${paths.requirementsRoot}/\` |`,
24763
+ `| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Where the upstream \`requirements-analyst\` writes proposals | \`${paths.researchRequirementsRoot}/\` |`,
24764
+ `| \`<TEMPLATES_ROOT>\` | Where the category templates ship (set automatically by this bundle) | \`${REQUIREMENTS_WRITER_PATHS.templatesRoot}\` |`,
24765
+ `| \`<STANDARDS_REF>\` | Standards & frameworks reference document | \`${REQUIREMENTS_WRITER_PATHS.standardsRef}\` |`,
24766
+ `| \`<PREFIX>\` | Project-specific requirement ID prefix | none \u2014 requirement IDs use the category prefix (\`BR\`, \`FR\`, \u2026) unless \`${paths.docsRoot}/project-context.md\` defines a project prefix |`,
24767
+ "",
24768
+ `If \`${paths.docsRoot}/project-context.md\` specifies a different requirements tree,`,
24769
+ "prefer that. Otherwise fall back to the defaults above.",
24770
+ "",
24771
+ "---",
24772
+ "",
24773
+ ...REQUIREMENTS_TAXONOMY_TABLE_SECTION,
24774
+ "",
24775
+ "### Disambiguation Rules",
24776
+ "",
24777
+ "Apply these rules when deciding where a requirement belongs:",
24778
+ "",
24779
+ ...REQUIREMENTS_TAXONOMY_DISAMBIGUATION_SECTION,
24780
+ "",
24781
+ "---",
24782
+ "",
24783
+ "## Architectural Tiers",
24784
+ "",
24785
+ "Every requirement also belongs to one of four architectural tiers.",
24786
+ 'Tier is **orthogonal** to category \u2014 category answers "what kind"',
24787
+ 'while tier answers "where in the architecture." The tier names below',
24788
+ "are the bundle defaults; consuming projects may rename or reduce the",
24789
+ `tier set in their own \`${paths.docsRoot}/project-context.md\`.`,
24790
+ "",
24791
+ ...REQUIREMENTS_TIER_TABLE_SECTION,
24792
+ "",
24793
+ "Tier is a **required metadata field**. Set it in the Metadata table",
24794
+ "of every requirement document and apply the matching `tier:*` issue",
24795
+ "label (`tier:platform`, `tier:industry`, `tier:customer-workflow`,",
24796
+ "or `tier:consumer-app`).",
24797
+ "",
24798
+ "**Customer field (optional).** When the proposal originated from or",
24799
+ "applies to a specific customer, set the `Customer` field in the",
24800
+ "Metadata table. Optional for Platform/Industry tiers, expected for",
24801
+ "Customer Workflow / Consumer Application tiers when the project",
24802
+ "tracks customer profiles.",
24803
+ "",
24804
+ "**Implementor field (tier-specific).** For Customer Workflow and",
24805
+ "Consumer Application tiers, set the `Implementor` field (Consortium",
24806
+ "Member / Customer / TBD) when the project tracks that distinction.",
24807
+ "Skip the field entirely if it does not apply to your project.",
24808
+ "",
24809
+ "**Cross-tier traceability.** Higher-tier requirements must link to",
24810
+ "lower-tier requirements they depend on using `Depends on (cross-tier)`",
24811
+ "links in the Traceability section. For example, a Consumer",
24812
+ "Application FR for an intake portal must trace to the Platform INT",
24813
+ "for the API it consumes.",
24814
+ "",
24815
+ "---",
24816
+ "",
24817
+ "## Decision Authority Rules",
24818
+ "",
24819
+ "**This is the most important section.** Not all categories are",
24820
+ "treated equally. The core principle is: **requirements that describe",
24821
+ "*what* and *why* get written directly; decisions about *how* and",
24822
+ "*with what* are deferred to humans.**",
24823
+ "",
24824
+ "### Write Directly (status: `Draft`)",
24825
+ "",
24826
+ "These categories describe business needs, user behavior, quality",
24827
+ "targets, security posture, and user experience. The writer has full",
24828
+ "authority to produce these documents:",
24829
+ "",
24830
+ "- **BR** \u2014 Business Requirements",
24831
+ "- **FR** \u2014 Functional Requirements",
24832
+ "- **NFR** \u2014 Non-Functional Requirements",
24833
+ "- **SEC** \u2014 Security & Compliance",
24834
+ "- **UX** \u2014 UX Requirements",
24835
+ "",
24836
+ "### Write with Partial Deferral",
24837
+ "",
24838
+ "These categories contain a mix of *what* (write directly) and *how*",
24839
+ "(defer). Write the requirement portions directly but defer",
24840
+ "technology/tooling selections:",
24841
+ "",
24842
+ "- **DR** \u2014 Write data models, retention policies, and classification",
24843
+ " directly. When backup strategy or storage technology must be",
24844
+ " specified, create a `Proposed` ADR or TR instead of choosing.",
24845
+ "- **MT** \u2014 Write tenant isolation requirements and metering",
24846
+ " dimensions directly. When the isolation *model* must be chosen",
24847
+ " (row-level vs schema-per-tenant vs database-per-tenant), create a",
24848
+ " `Proposed` ADR instead of choosing.",
24849
+ "- **INT** \u2014 Write the integration shape, direction, error handling",
24850
+ " strategy, and SLA expectations directly. When the specific",
24851
+ " provider must be selected, create a `Proposed` TR/ADR instead.",
24852
+ "- **OPS** \u2014 Write operational requirements (monitoring needs,",
24853
+ " incident response, deployment strategy) directly. When specific",
24854
+ " tooling must be selected, create a `Proposed` TR/ADR instead.",
24855
+ "",
24856
+ "### Propose Only (status: `Proposed`, pending human decision)",
24857
+ "",
24858
+ "These categories represent technology and architecture decisions",
24859
+ "that humans must make. Produce complete documents with all the",
24860
+ "information needed for the decision, but **do not make the decision",
24861
+ "itself**.",
24862
+ "",
24863
+ "- **ADR** \u2014 Write the full document following the ADR template:",
24864
+ " 1. **Context** \u2014 forces at play, constraints, what prompted the",
24865
+ " decision",
24866
+ ' 2. **Decision** \u2014 set to: *"Pending human review. See',
24867
+ ' Recommendation below."*',
24868
+ " 3. **Alternatives Considered** \u2014 every viable option with",
24869
+ " equal-depth analysis. Each gets a description, pros, and cons.",
24870
+ " Do not shortchange options you don't prefer.",
24871
+ " 4. **Recommendation** \u2014 state which option you recommend, *why*",
24872
+ " it's the best fit for the context, and what trade-offs are",
24873
+ " accepted. The reasoning must be specific to this project's",
24874
+ " situation \u2014 not generic.",
24875
+ " 5. **Consequences** \u2014 positive, negative, and neutral consequences",
24876
+ " of the recommended option",
24877
+ " 6. Set status to `Proposed`. Add an open item flagging that a",
24878
+ " human decision is required before dependent requirements can",
24879
+ " proceed.",
24880
+ "",
24881
+ "- **TR** \u2014 Write the full document following the TR template:",
24882
+ ' 1. **Technology Choice** \u2014 set to: *"Pending human review. See',
24883
+ ' Recommendation below."*',
24884
+ " 2. **Alternatives Considered** \u2014 every viable technology option",
24885
+ " with description, pros, cons, license, and maturity assessment",
24886
+ " for each",
24887
+ " 3. **Recommendation** \u2014 state which technology you recommend,",
24888
+ " *why*, and what trade-offs are accepted",
24889
+ ' 4. **Rationale** \u2014 set to: *"See Recommendation above. Awaiting',
24890
+ ' human decision."*',
24891
+ " 5. Set status to `Proposed`. Add an open item flagging that a",
24892
+ " human decision is required.",
24893
+ "",
24894
+ "### How Deferral Works in Practice",
24895
+ "",
24896
+ "When writing a requirement that implies a technology choice:",
24897
+ "",
24898
+ "1. Write the requirement itself (FR, DR, INT, OPS, etc.) with full",
24899
+ " detail.",
24900
+ "2. Where the requirement needs a technology decision, add a note:",
24901
+ ` *"Technology selection pending \u2014 see [ADR-NNN](../${paths.requirementCategoryDirs.architecturalDecisions}/ADR-NNN-slug.md)"*.`,
24902
+ "3. Create the corresponding ADR or TR as `Proposed` with options,",
24903
+ " pros/cons, and recommendation.",
24904
+ '4. In the ADR/TR open items, add: *"Human decision required.',
24905
+ ' Dependent requirements: [list]"*.',
24906
+ "5. In the original requirement's open items, add a cross-reference:",
24907
+ ` *"Blocked by [ADR-NNN](../${paths.requirementCategoryDirs.architecturalDecisions}/ADR-NNN-slug.md) \u2014`,
24908
+ ' technology selection pending human review."*',
24909
+ "",
24910
+ "This creates a clear chain: the requirement is understood, the",
24911
+ "decision is framed with all necessary information, and the",
24912
+ "dependency is tracked in both directions.",
24913
+ "",
24914
+ "---",
24915
+ "",
24916
+ "## File Naming Convention",
24917
+ "",
24918
+ "Every requirement file follows this pattern:",
24919
+ "",
24920
+ "```",
24921
+ "{PREFIX}-{NNN}-{slug}.md",
24922
+ "```",
24923
+ "",
24924
+ "- **PREFIX** \u2014 category abbreviation (`BR`, `FR`, `NFR`, `TR`,",
24925
+ " `ADR`, `SEC`, `DR`, `INT`, `OPS`, `UX`, `MT`). If the project",
24926
+ ` declares a project-wide prefix in \`${paths.docsRoot}/project-context.md\`, use`,
24927
+ " it instead.",
24928
+ "- **NNN** \u2014 sequential number, zero-padded to at least three",
24929
+ " digits (001, 002, 012; grows past 999 naturally, e.g. 1240). The",
24930
+ " ID is **finalized at write-start** via",
24931
+ " `.claude/procedures/next-requirement-id.sh`, which allocates from",
24932
+ " live state (existing docs plus open `req:write` issues). Scanning",
24933
+ " the target category directory under `<REQUIREMENTS_ROOT>` for the",
24934
+ " next available number is the **fallback** used only when that",
24935
+ " procedure is unavailable.",
24936
+ "- **slug** \u2014 lowercase kebab-case descriptive name.",
24937
+ "",
24938
+ "Examples: `FR-001-user-registration.md`,",
24939
+ "`ADR-003-database-selection.md`,",
24940
+ "`SEC-001-authentication-framework.md`.",
24941
+ "",
24942
+ "---",
24943
+ "",
24944
+ "## Frontmatter",
24945
+ "",
24946
+ "Every `.md` requirement file must begin with a YAML frontmatter",
24947
+ "block. The minimum required fields are `title` and `description`:",
24948
+ "",
24949
+ "```markdown",
24950
+ "---",
24951
+ 'title: "FR-001: User Registration"',
24952
+ 'description: "Functional requirement for the user registration workflow."',
24953
+ "tier: platform",
24954
+ "---",
24955
+ "",
24956
+ "...",
24957
+ "```",
24958
+ "",
24959
+ "Titles containing colons must be wrapped in double quotes. The",
24960
+ "`tier` field must be one of `platform`, `industry`,",
24961
+ "`customer-workflow`, or `consumer-app` (use whatever tier slugs the",
24962
+ "project declares).",
24963
+ "",
24964
+ "### Optional traceability extensions",
24965
+ "",
24966
+ "Projects that publish requirements through Starlight, Astro, or a",
24967
+ "similar static-site generator may add structured traceability fields",
24968
+ "to frontmatter. One example: a `referencedIn.meetings[]` block that",
24969
+ "links the requirement back to a meeting transcript that informed it.",
24970
+ "These conventions are **optional** \u2014 adopt them only if your project",
24971
+ "already maintains the matching reverse-link structures.",
24972
+ "",
24973
+ "---",
24974
+ "",
24975
+ "## Status Lifecycle",
24976
+ "",
24977
+ "Every requirement has a status that tracks where it is in its",
24978
+ "lifecycle:",
24979
+ "",
24980
+ "| Status | Meaning |",
24981
+ "|---|---|",
24982
+ "| `Draft` | Initial capture, under discussion |",
24983
+ "| `Proposed` | Formally proposed, awaiting review \u2014 **used for ADR and TR documents pending human decision** |",
24984
+ "| `Accepted` | Approved and active |",
24985
+ "| `Implemented` | Fully delivered |",
24986
+ "| `Deprecated` | No longer applicable |",
24987
+ "| `Superseded` | Replaced by another requirement (link to successor) |",
24988
+ "",
24989
+ "---",
24990
+ "",
24991
+ "## Traceability",
24992
+ "",
24993
+ "Every requirement document must include a `## Traceability` section.",
24994
+ "Use relative markdown links with the requirement ID as link text.",
24995
+ "",
24996
+ "```markdown",
24997
+ "## Traceability",
24998
+ "",
24999
+ `- **Implements:** [BR-001](../${paths.requirementCategoryDirs.business}/BR-001-self-service-onboarding.md)`,
25000
+ `- **Constrained by:** [NFR-001](../${paths.requirementCategoryDirs.nonFunctional}/NFR-001-api-response-times.md)`,
25001
+ `- **Related:** [SEC-001](../${paths.requirementCategoryDirs.security}/SEC-001-authentication-framework.md)`,
25002
+ "```",
25003
+ "",
25004
+ "### Link Types",
25005
+ "",
25006
+ "| Link Type | Meaning |",
25007
+ "|---|---|",
25008
+ "| **Implements** | This requirement realizes a higher-level requirement |",
25009
+ "| **Constrained by** | This requirement is bounded by another requirement |",
25010
+ "| **Supports** | This requirement contributes to but does not fully realize another |",
25011
+ "| **Supersedes** | This requirement replaces a deprecated one |",
25012
+ "| **Related** | Informational relationship |",
25013
+ "",
25014
+ "### Dependency Flow",
25015
+ "",
25016
+ "```",
25017
+ "Business Requirements (BR)",
25018
+ " -> justify epics and initiatives",
25019
+ "",
25020
+ "Functional (FR), Integration (INT), Multi-Tenancy (MT)",
25021
+ " -> decompose into feature work",
25022
+ "",
25023
+ "NFRs, Security (SEC), UX",
25024
+ " -> appear as acceptance criteria AND as standalone requirements",
25025
+ "",
25026
+ "Architectural Decisions (ADR)",
25027
+ " -> spawn implementation tasks and serve as context for TRs",
25028
+ "",
25029
+ "Technical (TR), Data (DR), Operational (OPS)",
25030
+ " -> drive infrastructure and platform work",
25031
+ "```",
25032
+ "",
25033
+ "---",
25034
+ "",
25035
+ "## Open Items",
25036
+ "",
25037
+ "Every requirement document must end with a `## Open Items` section",
25038
+ "(placed just above `## Revision History`). This section surfaces",
25039
+ "things that need human attention.",
25040
+ "",
25041
+ "### Priority Levels",
25042
+ "",
25043
+ "| Priority | Meaning |",
25044
+ "|---|---|",
25045
+ "| **P0 \u2014 Blocker** | Cannot proceed with implementation until resolved |",
25046
+ "| **P1 \u2014 High** | Significantly affects scope, security, or architecture |",
25047
+ "| **P2 \u2014 Medium** | Affects quality or completeness but doesn't block progress |",
25048
+ "| **P3 \u2014 Low** | Minor clarification, can be resolved asynchronously |",
25049
+ "",
25050
+ "### Subsection Types",
25051
+ "",
25052
+ "- **Identified Gaps** \u2014 Missing functionality, unhandled edge cases,",
25053
+ " incomplete aspects",
25054
+ "- **Follow-up Questions** \u2014 Ambiguities where intent cannot be",
25055
+ " confidently inferred",
25056
+ "- **Contradictions & Inconsistencies** \u2014 Conflicts between",
25057
+ " requirements, documentation, or stated goals",
25058
+ "",
25059
+ "Number each item within its subsection. Assign a priority. Call out",
25060
+ "interdependencies with open items in other documents using the",
25061
+ `format: \`-> Depends on: [FR-001 Open Item #2](../${paths.requirementCategoryDirs.functional}/FR-001-slug.md#open-items)\`.`,
25062
+ "",
25063
+ "The `## Open Items` section is **not optional** \u2014 it appears in every",
25064
+ 'document, even if a subsection is empty (write "None identified.").',
25065
+ "",
25066
+ "---",
25067
+ "",
25068
+ "## Reading the Templates and Standards Reference",
25069
+ "",
25070
+ "Before writing any requirement document:",
25071
+ "",
25072
+ "1. **Read the matching template** under `<TEMPLATES_ROOT>` for the",
25073
+ " category specified in the issue (`_template-FR.md`,",
25074
+ " `_template-ADR.md`, etc.). Templates are named",
25075
+ " `_template-{PREFIX}.md`. Every section in the template must",
25076
+ " appear in the final document.",
25077
+ "",
25078
+ "2. **Read `<STANDARDS_REF>`** if the category requires it (especially",
25079
+ " SEC, NFR, INT, ADR, TR). The reference covers OWASP ASVS, ISO",
25080
+ " 25010, BABOK, MADR, Enterprise Integration Patterns, WCAG, and",
25081
+ " related standards that ground the templates.",
25082
+ "",
25083
+ "Templates and the standards reference ship with this skill \u2014 they",
25084
+ "are the same files for every project that adopts the bundle.",
25085
+ "",
25086
+ "---",
25087
+ "",
25088
+ "## Maintaining the Registry Index",
25089
+ "",
25090
+ "Each category directory contains a `README.md` (or `_index.md`,",
25091
+ "depending on project convention) that lists every requirement in",
25092
+ "that category. **After writing any new requirement document, update",
25093
+ "the category index:**",
25094
+ "",
25095
+ "1. Read the index file in the same directory as the new requirement.",
25096
+ "2. Add a row to the requirements table with: ID (linked to the",
25097
+ " file), Title, Status, Priority, Last Updated date.",
25098
+ "3. Insert the row in sequence-number order.",
25099
+ "4. If the index contains a placeholder message, remove it.",
25100
+ "",
25101
+ "If the project has not seeded category READMEs yet, generate one",
25102
+ "from `_template-category-README.md` (shipped with this skill) and",
25103
+ "commit it alongside the requirement document.",
25104
+ "",
25105
+ "---",
25106
+ "",
25107
+ "## Agent Loop",
25108
+ "",
25109
+ "Run this loop exactly once per session. Never start a second issue.",
25110
+ "",
25111
+ "1. Claim one open `req:write` issue (also carries `type:requirement`",
25112
+ " and a `tier:*` label).",
25113
+ "2. Transition `status:ready` \u2192 `status:in-progress` and create the",
25114
+ " branch per your project's branch-naming convention.",
25115
+ "3. Execute the write phase below.",
25116
+ "4. Commit, push, open a PR, and close the issue per your project's",
25117
+ " PR workflow. Closing the PR transitions the issue to",
25118
+ " `status:done` per the standard label conventions.",
25119
+ "",
25120
+ "---",
25121
+ "",
25122
+ "## The `req:write` Issue Schema",
25123
+ "",
25124
+ "This section is the **authoritative schema** for `req:write` issue",
25125
+ "bodies. Upstream producers (the `requirements-analyst` trace phase,",
25126
+ "the `requirements-reviewer` follow-up generator, and any human",
25127
+ "authoring a `req:write` issue directly) must embed or link to this",
25128
+ "same schema so the three required fields are always present when",
25129
+ "the writer picks the issue up.",
25130
+ "",
25131
+ ...REQ_WRITE_ISSUE_SCHEMA_SECTION,
25132
+ "",
25133
+ "---",
25134
+ "",
25135
+ "## The `req:write` Phase",
25136
+ "",
25137
+ "**Goal:** Read the proposal that produced this issue, write a single",
25138
+ "requirement document under `<REQUIREMENTS_ROOT>`, and update the",
25139
+ "category index.",
25140
+ "",
25141
+ "**Budget:** Read the issue body, the named proposal file under",
25142
+ "`<RESEARCH_REQUIREMENTS_ROOT>`, the matching category template under",
25143
+ "`<TEMPLATES_ROOT>`, and `<STANDARDS_REF>` if the category needs it.",
25144
+ "Write one requirement document and one category-index update. No web",
25145
+ "searches.",
25146
+ "",
25147
+ "### Steps",
25148
+ "",
25149
+ "1. **Parse the issue.** The `req:write` issue body follows the",
25150
+ " authoritative schema in the",
25151
+ " [The `req:write` Issue Schema](#the-reqwrite-issue-schema)",
25152
+ " section below. Before writing, verify every required field",
25153
+ " (Category, Tier, Output Path) is present and internally",
25154
+ " consistent:",
25155
+ "",
25156
+ " - Category matches the prefix on the requirement title and on",
25157
+ " the Output Path filename.",
25158
+ " - Tier matches the `tier:*` label applied to the issue.",
25159
+ " - Output Path sits under",
25160
+ " `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`.",
25161
+ "",
25162
+ " If any required field is missing, contradictory, or the issue",
25163
+ " already carries `status:needs-attention` with a `Missing:` line",
25164
+ " (upstream could not derive it), comment on the issue with",
25165
+ " which field is wrong, ensure `status:needs-attention` is set,",
25166
+ " and stop without writing. A human triages before the writer",
25167
+ " tries again.",
25168
+ "",
25169
+ "2. **Read the proposal.** Open the proposals file referenced in the",
25170
+ " issue inputs (under `<RESEARCH_REQUIREMENTS_ROOT>`). Find the",
25171
+ " specific proposal entry that matches this requirement's title and",
25172
+ " category. Treat the proposal as the authoritative source \u2014 do not",
25173
+ " invent fields it omits.",
25174
+ "",
25175
+ "3. **Read the matching template** under `<TEMPLATES_ROOT>` for the",
25176
+ " category. Read `<STANDARDS_REF>` if the category is SEC, NFR,",
25177
+ " INT, ADR, or TR \u2014 these depend on the standards reference for",
25178
+ " correct framing.",
25179
+ "",
25180
+ "4. **Finalize the requirement ID first \u2014 before you author any",
25181
+ " document content, filename, or self-reference.** Run",
25182
+ " `.claude/procedures/next-requirement-id.sh <PREFIX> <category-dir> <this-issue-number>`",
25183
+ " to compute the next free ID from live state (existing docs plus",
25184
+ " open `req:write` issues, with the issue-number tiebreak). Use",
25185
+ " `<category-dir>` = the resolved `<REQUIREMENTS_ROOT>/<category-dir>`",
25186
+ " for the target category, and `<this-issue-number>` = the number of",
25187
+ " the `req:write` issue you are working.",
25188
+ "",
25189
+ " - If the issue's reserved `Requirement ID` hint is present **and",
25190
+ " equals** the procedure's output, use it.",
25191
+ " - Otherwise use the **procedure's output** \u2014 a concurrent claim",
25192
+ " took the reserved number, and the procedure has already resolved",
25193
+ " the collision in your favor via the issue-number order.",
25194
+ "",
25195
+ " **Author the document exactly once with this finalized ID** \u2014 the",
25196
+ " filename and every in-body self-reference. **Never renumber after",
25197
+ " authoring:** the ID is fixed before any body content exists, so a",
25198
+ " concurrent claim is resolved here for free \u2014 no rename, no",
25199
+ " self-reference rewrite, no post-authoring renumber treadmill.",
25200
+ "",
25201
+ " After finalizing, and before opening the PR, you SHOULD update this",
25202
+ " issue's `Requirement ID` field to the finalized value",
25203
+ " (`gh issue edit <this-issue-number>`) so other in-flight writers",
25204
+ " see the resolved claim. This is best-effort \u2014 do not block or fail",
25205
+ " the write if the edit does not succeed.",
25206
+ "",
25207
+ " If `next-requirement-id.sh` is unavailable, fall back to scanning",
25208
+ " the target category directory under `<REQUIREMENTS_ROOT>` for",
25209
+ " existing files matching `{PREFIX}-NNN-*.md` and take the next",
25210
+ " unused three-digit number.",
25211
+ "",
25212
+ "5. **Apply the decision-authority rules.** Default to `Status:",
25213
+ " Draft` for direct-write categories (BR, FR, NFR, SEC, UX). Set",
25214
+ " `Status: Proposed` for ADR and TR documents and frame the",
25215
+ " Recommendation section as a human decision. For DR/MT/INT/OPS",
25216
+ " categories that imply a technology choice, write the requirement",
25217
+ " directly but spin the technology decision off into a separate",
25218
+ " `Proposed` ADR or TR \u2014 record the cross-link in the Open Items",
25219
+ " section of both documents.",
25220
+ "",
25221
+ "6. **Write the document.** Fill every section in the template. For",
25222
+ " sections the proposal does not supply, write `TODO:` plus a brief",
25223
+ " note describing what input is needed, and add a corresponding",
25224
+ " `## Open Items` entry. Never leave a template section out.",
25225
+ "",
25226
+ "7. **Set frontmatter.** At minimum: `title`, `description`, `tier`.",
25227
+ " If the project declares optional frontmatter conventions in",
25228
+ ` \`${paths.docsRoot}/project-context.md\` (such as \`referencedIn.meetings[]\`),`,
25229
+ " honor them. Otherwise stop at the minimum.",
25230
+ "",
25231
+ "8. **Update the category index.** If the category directory has a",
25232
+ " `README.md` or `_index.md` registry, add a row for the new",
25233
+ " document in sequence order. If no index exists yet, generate one",
25234
+ " from `_template-category-README.md`. The requirement ID was",
25235
+ " already finalized at write-start (step 4), so this is a plain",
25236
+ " append-only, deterministic row insert \u2014 never renumber existing",
25237
+ " rows or mutate a sequence counter here.",
25238
+ "",
25239
+ "9. **Cross-link upstream.** Add `## Traceability` entries pointing",
25240
+ " to the proposals file (under `<RESEARCH_REQUIREMENTS_ROOT>`),",
25241
+ " the source documents the proposal cited, and any BCM capability",
25242
+ " the requirement supports.",
25243
+ "",
25244
+ "10. **Quality checks.** Before committing, verify:",
25245
+ " - [ ] Frontmatter has `title` and `description` (and `tier` if",
25246
+ " the project uses tier classification)",
25247
+ " - [ ] Title matches the `# Heading` line",
25248
+ " - [ ] File name follows `{PREFIX}-{NNN}-{slug}.md`",
25249
+ " - [ ] Status is `Draft` for direct-write categories or `Proposed`",
25250
+ " for ADR/TR",
25251
+ " - [ ] Every template section is present (use `TODO:` or `Not",
25252
+ " applicable \u2014 <reason>` for unfilled sections)",
25253
+ " - [ ] `## Traceability` exists with at least one upstream link",
25254
+ " - [ ] No technology decisions made in direct-write categories \u2014",
25255
+ " deferred to `Proposed` ADR/TR with cross-links in Open",
25256
+ " Items",
25257
+ " - [ ] `## Open Items` is present with Identified Gaps,",
25258
+ " Follow-up Questions, and Contradictions subsections",
25259
+ " - [ ] ADR/TR documents include a Recommendation section and an",
25260
+ " Open Item flagging the human decision required",
25261
+ " - [ ] Category index updated with a row for the new document",
25262
+ " - [ ] Tier value matches the issue's `tier:*` label",
25263
+ " - [ ] Cross-tier traceability entries exist where the document",
25264
+ " depends on or enables a different tier",
25265
+ "",
25266
+ "11. **Commit and push.** Use a `docs(<category>):` conventional",
25267
+ " commit message. The PR closes the `req:write` issue.",
25268
+ "",
25269
+ "---",
25270
+ "",
25271
+ "## Output Boundaries",
25272
+ "",
25273
+ "This agent writes **only** to:",
25274
+ "",
25275
+ "- `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md` \u2014 one",
25276
+ " requirement document per session",
25277
+ "- `<REQUIREMENTS_ROOT>/<category-dir>/README.md` (or `_index.md`) \u2014",
25278
+ " category index, one row appended per session",
25279
+ "- `<REQUIREMENTS_ROOT>/README.md` (or `_index.md`) \u2014 only if the",
25280
+ " top-level requirements README does not yet exist; generate from",
25281
+ " `_template-requirements-README.md` and stop",
25282
+ "",
25283
+ "The pipeline produces **requirement documents**. It does not write",
25284
+ "BCM capability models, people profiles, company profiles, software",
25285
+ "profiles, scan reports, or proposal files \u2014 those belong to",
25286
+ "specialized upstream/downstream agents.",
25287
+ "",
25288
+ "**Do NOT create:**",
25289
+ "- `req:scan` or `req:draft-trace` issues \u2014 those belong to",
25290
+ " the `requirements-analyst` bundle",
25291
+ "- `bcm:*` issues \u2014 those belong to the `bcm-writer` bundle",
25292
+ "- `people:*`, `company:*`, `software:*`, `research:*`, or",
25293
+ " `industry:*` issues \u2014 those belong to their respective bundles",
25294
+ "",
25295
+ "If the proposal surfaces work that needs one of the above, comment",
25296
+ "on the `req:write` issue with the suggested follow-up and let a",
25297
+ "human route it.",
25298
+ "",
25299
+ "---",
25300
+ "",
25301
+ "## Coordination with Other Agents",
25302
+ "",
25303
+ "| Direction | Agent | What |",
25304
+ "|-----------|-------|------|",
25305
+ "| Upstream | `requirements-analyst` | Discovers gaps, drafts proposals, and creates `req:write` issues that this agent picks up |",
25306
+ "| Upstream | `bcm-writer` | Provides BCM capability documents that requirements trace back to via `## Traceability` links |",
25307
+ "| Peer | `meeting-analyst` | Provides meeting transcripts that may inform a requirement's traceability extensions (optional) |",
25308
+ "",
25309
+ "**File boundaries:** Reads `<RESEARCH_REQUIREMENTS_ROOT>` (proposals)",
25310
+ "and the source documents the proposals cite. Writes",
25311
+ "`<REQUIREMENTS_ROOT>` and the category index files. Never edits",
25312
+ "proposals, scan reports, BCM documents, or profiles.",
25313
+ "",
25314
+ "---",
25315
+ "",
25316
+ "## Rules",
25317
+ "",
25318
+ "- **One requirement per session.** Never write two documents in one",
25319
+ " session and never start a second issue.",
25320
+ "- **Templates are authoritative.** Use the shipped template verbatim",
25321
+ " for the category. Every template section must appear in the final",
25322
+ " document.",
25323
+ "- **Decision authority is non-negotiable.** Direct-write categories",
25324
+ " ship as `Draft`. ADR and TR ship as `Proposed` with a Recommendation",
25325
+ " framed for human decision. Mixed-deferral categories spin",
25326
+ " technology choices off into separate `Proposed` documents.",
25327
+ "- **Cite, don't invent.** When the proposal omits a stakeholder,",
25328
+ " metric, threat model entry, or technology option, write `TODO:` and",
25329
+ " flag the issue with `status:needs-attention`.",
25330
+ "- **Trace upstream.** Every requirement links back to its proposal,",
25331
+ " the source documents the proposal cited, and the BCM capability",
25332
+ " it supports (when applicable).",
25333
+ "- **Update the category index every time.** A requirement that",
25334
+ " exists as a file but is missing from its category index is",
25335
+ " invisible to anyone browsing the documentation tree.",
25336
+ "- **Write requirements, not capability models or gap reports.**",
25337
+ " Never open `req:scan`, `req:draft-trace`, or `bcm:*` issues from",
25338
+ " this pipeline."
25339
+ ].join("\n")
25340
+ };
25341
+ }
25342
+ function buildWriteRequirementSkill(paths, issueDefaults) {
25343
+ return {
25344
+ name: WRITE_REQUIREMENT_SKILL_NAME,
25345
+ description: "Write one formal requirement document (BR / FR / NFR / TR / ADR / SEC / DR / INT / OPS / UX / MT) using the shipped category template and decision-authority rules. Picks up a req:write issue created by the upstream requirements-analyst pipeline (or kicked off ad hoc) and dispatches the requirements-writer agent.",
25346
+ disableModelInvocation: true,
25347
+ userInvocable: true,
25348
+ context: "fork",
25349
+ agent: "requirements-writer",
25350
+ platforms: { cursor: { exclude: true } },
25351
+ referenceFiles: buildRequirementsWriterReferenceFiles(paths),
25352
+ instructions: [
25353
+ "# Write Requirement",
25354
+ "",
25355
+ "Write one formal requirement document using the 11-category taxonomy",
25356
+ "(BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) and the",
25357
+ "decision-authority rules (direct-write vs. propose-only ADR/TR).",
25358
+ "Dispatches the `requirements-writer` agent.",
25359
+ "",
25360
+ "## Usage",
25361
+ "",
25362
+ "/write-requirement <category> <short-title>",
25363
+ "",
25364
+ "Where `<category>` is one of `BR`, `FR`, `NFR`, `TR`, `ADR`, `SEC`,",
25365
+ "`DR`, `INT`, `OPS`, `UX`, `MT`.",
25366
+ "",
25367
+ "Optional extensions in the issue body:",
25368
+ "- `tier: platform | industry | customer-workflow | consumer-app` \u2014",
25369
+ " the architectural tier (default: `platform`)",
25370
+ "- `prefix: <PROJECT_PREFIX>` \u2014 override the default category prefix",
25371
+ ` with a project-specific one declared in \`${paths.docsRoot}/project-context.md\``,
25372
+ "- `customer: <link-or-slug>` \u2014 link the requirement to a customer",
25373
+ " profile (expected for Customer Workflow / Consumer Application",
25374
+ " tiers in projects that track customer profiles)",
25375
+ "- `proposal: <path>` \u2014 pin the upstream proposal file under",
25376
+ " `<RESEARCH_REQUIREMENTS_ROOT>` (default: derived from the issue",
25377
+ " context)",
25378
+ "- `output: <path>` \u2014 override the default Output Path",
25379
+ "",
25380
+ "## Default Paths",
25381
+ "",
25382
+ `If the project has no override in \`${paths.docsRoot}/project-context.md\` or`,
25383
+ "`agentConfig.rules`, outputs land under:",
25384
+ "",
25385
+ `- \`${paths.requirementsRoot}/<category-dir>/<PREFIX>-<NNN>-<slug>.md\``,
25386
+ `- \`${paths.requirementsRoot}/<category-dir>/README.md\` (registry update)`,
25387
+ `- \`${paths.requirementsRoot}/README.md\` (top-level README, generated on`,
25388
+ " first use only)",
25389
+ "",
25390
+ "Templates and the standards reference ship with this skill under",
25391
+ "`_references/templates/` and `_references/standards-and-frameworks.md`.",
25392
+ "",
25393
+ "## Steps",
25394
+ "",
25395
+ "1. Create a `req:write` issue with `type:requirement`,",
25396
+ ` \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\`, \`status:${labelsForPhase(issueDefaults, "req:write").status}\`, and the matching \`tier:*\` label.`,
25397
+ " Body must include the category, tier, output path, and a pointer",
25398
+ " to the upstream proposal (or a direct user description if no",
25399
+ " proposals file exists).",
25400
+ "2. Execute the write phase of the requirements-writer agent.",
25401
+ "3. The agent writes one requirement document, updates the category",
25402
+ " index, opens a PR, and closes the issue.",
25403
+ "",
25404
+ "## Output",
25405
+ "",
25406
+ "- One requirement document under `<REQUIREMENTS_ROOT>` following the",
25407
+ " shipped category template, with `Status: Draft` for direct-write",
25408
+ " categories or `Status: Proposed` for ADR/TR",
25409
+ "- A category-index row pointing at the new document",
25410
+ "- (First-time only) a top-level requirements README derived from",
25411
+ " `_template-requirements-README.md`"
25412
+ ].join("\n")
25413
+ };
25414
+ }
25415
+ function renderNextRequirementIdProcedure() {
25416
+ return [
25417
+ "#!/usr/bin/env bash",
25418
+ "# next-requirement-id.sh \u2014 print the next free requirement ID for a",
25419
+ "# category, allocated from live GitHub state (open req:write issues)",
25420
+ "# plus the merged docs already on disk.",
25421
+ "#",
25422
+ "# Usage:",
25423
+ "# .claude/procedures/next-requirement-id.sh <PREFIX> <category-dir> [<self-issue-number>]",
25424
+ "#",
25425
+ "# Example:",
25426
+ "# next-requirement-id.sh FR docs/src/content/docs/requirements/functional 850",
25427
+ "# -> FR-1240",
25428
+ "#",
25429
+ "# Behaviour:",
25430
+ "# - Collects candidate NNN numbers from (1) files in <category-dir>",
25431
+ "# matching ^<PREFIX>-([0-9]+)-.*\\.md$ and (2) open req:write",
25432
+ "# issues whose body carries a `Requirement ID: <PREFIX>-<NNN>`",
25433
+ "# line. Prints `<PREFIX>-<max+1>` zero-padded to at least three",
25434
+ "# digits (grows past 999 naturally, e.g. FR-1240).",
25435
+ "# - When <self-issue-number> is given, only open req:write issues",
25436
+ "# with a STRICTLY LOWER number are counted. This excludes the",
25437
+ "# caller's own reservation and imposes an issue-number total",
25438
+ "# order so two concurrent writers never both pick the same",
25439
+ "# number. Omit it (analyst use, before any issue is filed) to",
25440
+ "# count every open req:write issue.",
25441
+ "#",
25442
+ "# Guards:",
25443
+ "# - Side-effect-free: never edits issues, docs, or any counter.",
25444
+ "# - Every gh/jq call is guarded (|| true, 2>/dev/null); a missing",
25445
+ "# binary, missing auth, or non-repo cwd contributes nothing and",
25446
+ "# the helper falls back to the directory scan.",
25447
+ "# - Prints a usage message to stderr and exits 2 on bad args; a",
25448
+ "# missing or empty <category-dir> is tolerated (scan finds",
25449
+ "# nothing). Happy path always exits 0.",
25450
+ "",
25451
+ "set -uo pipefail",
25452
+ "",
25453
+ "usage() {",
25454
+ ' printf "usage: next-requirement-id.sh <PREFIX> <category-dir> [<self-issue-number>]\\n" >&2',
25455
+ "}",
25456
+ "",
25457
+ "# Require at least a prefix and a category directory argument.",
25458
+ 'if [ "$#" -lt 2 ]; then',
25459
+ " usage",
25460
+ " exit 2",
25461
+ "fi",
25462
+ "",
25463
+ 'prefix="$1"',
25464
+ 'category_dir="$2"',
25465
+ 'self_issue="${3:-}"',
25466
+ "",
25467
+ "# The running maximum NNN seen across both candidate sources. Starts",
25468
+ "# at 0 so an empty category with no open issues yields <PREFIX>-001.",
25469
+ "max=0",
25470
+ "",
25471
+ "# Fold a bare integer candidate into the running max. Ignores any",
25472
+ "# value that is not a run of digits (defensive against malformed",
25473
+ "# filenames or issue bodies).",
25474
+ "consider() {",
25475
+ ' local n="$1"',
25476
+ ' case "$n" in',
25477
+ " '' | *[!0-9]*) return 0 ;;",
25478
+ " esac",
25479
+ " # Strip leading zeros for base-10 arithmetic (avoid octal parsing).",
25480
+ " n=$((10#$n))",
25481
+ ' if [ "$n" -gt "$max" ]; then',
25482
+ ' max="$n"',
25483
+ " fi",
25484
+ "}",
25485
+ "",
25486
+ "# --- Source 1: merged docs on disk -------------------------------------",
25487
+ "# Scan <category-dir> for files named <PREFIX>-<NNN>-<slug>.md and fold",
25488
+ "# each NNN into the running max. A missing/empty directory is a no-op.",
25489
+ 'if [ -n "$category_dir" ] && [ -d "$category_dir" ]; then',
25490
+ ' for f in "$category_dir"/"$prefix"-*.md; do',
25491
+ " # Guard the literal-glob case when nothing matches.",
25492
+ ' [ -e "$f" ] || continue',
25493
+ ' base="$(basename "$f")"',
25494
+ " # Extract the digit run between the prefix and the next hyphen.",
25495
+ ' nnn="${base#"$prefix"-}"',
25496
+ ' nnn="${nnn%%-*}"',
25497
+ ' consider "$nnn"',
25498
+ " done",
25499
+ "fi",
25500
+ "",
25501
+ "# --- Source 2: live state \u2014 open req:write issues ----------------------",
25502
+ "# Ask GitHub for open req:write issues and parse each body for a",
25503
+ "# `Requirement ID: <PREFIX>-<NNN>` field. The whole block is guarded so",
25504
+ "# a missing gh/jq, missing auth, or non-repo cwd simply yields no",
25505
+ "# candidates and the directory scan stands alone.",
25506
+ "if command -v gh >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then",
25507
+ ' issues_json="$(gh issue list --label req:write --state open --limit 500 --json number,body 2>/dev/null || true)"',
25508
+ ' if [ -n "$issues_json" ]; then',
25509
+ " # Flatten each issue to a single `<number>\\t<body>` line (newlines",
25510
+ " # in the body collapsed to spaces) so a plain read loop can walk",
25511
+ " # them. jq is guarded; on any parse error `parsed` is empty and the",
25512
+ " # loop runs zero times. Captured into a variable first so the loop",
25513
+ " # runs in THIS shell (a piped `while` would subshell and lose max).",
25514
+ ` parsed="$(printf '%s' "$issues_json" | jq -r '.[] | "\\(.number)\\t\\(.body // "" | gsub("[\\\\n\\\\r]"; " "))"' 2>/dev/null || true)"`,
25515
+ ' if [ -n "$parsed" ]; then',
25516
+ " while IFS=$'\\t' read -r issue_number issue_body; do",
25517
+ ' [ -n "$issue_number" ] || continue',
25518
+ " # Skip non-numeric issue numbers defensively.",
25519
+ ' case "$issue_number" in',
25520
+ " '' | *[!0-9]*) continue ;;",
25521
+ " esac",
25522
+ " # Issue-number total order: when a self issue is given, count",
25523
+ " # this issue only if it is STRICTLY lower-numbered than us.",
25524
+ ' if [ -n "$self_issue" ]; then',
25525
+ ' case "$self_issue" in',
25526
+ " '' | *[!0-9]*) : ;;",
25527
+ " *)",
25528
+ ' if [ "$issue_number" -ge "$self_issue" ]; then',
25529
+ " continue",
25530
+ " fi",
25531
+ " ;;",
25532
+ " esac",
25533
+ " fi",
25534
+ " # Pull the NNN out of a `Requirement ID: <PREFIX>-<NNN>` field.",
25535
+ " # Tolerant of surrounding markdown (**bold**, leading `- `) and",
25536
+ " # case-insensitive on the label. First match wins.",
25537
+ ` id_match="$(printf '%s\\n' "$issue_body" | grep -ioE "Requirement ID:[^A-Za-z0-9]*$prefix-[0-9]+" | head -n1 || true)"`,
25538
+ ' if [ -n "$id_match" ]; then',
25539
+ ` id_nnn="$(printf '%s' "$id_match" | grep -oE '[0-9]+$' || true)"`,
25540
+ ' consider "$id_nnn"',
25541
+ " fi",
25542
+ " done <<EOF",
25543
+ "$parsed",
25544
+ "EOF",
25545
+ " fi",
25546
+ " fi",
25547
+ "fi",
25548
+ "",
25549
+ "# next = max seen + 1, printed zero-padded to at least three digits.",
25550
+ "next=$((max + 1))",
25551
+ `printf '%s-%03d\\n' "$prefix" "$next"`,
25552
+ "",
25553
+ "exit 0"
25554
+ ].join("\n");
25555
+ }
25556
+ var nextRequirementIdProcedure = {
25557
+ name: "next-requirement-id.sh",
25558
+ description: "Prints the next free requirement ID (e.g. FR-1240) for a category, allocated from live GitHub state (open req:write issues, with an optional strictly-lower issue-number tiebreak) plus the merged docs on disk. Side-effect-free; every gh/jq call is guarded so a missing binary, missing auth, or non-repo cwd degrades to the directory scan. Exits 0 on the happy path.",
25559
+ content: renderNextRequirementIdProcedure()
25560
+ };
25561
+ function buildRequirementsWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
25562
+ return {
25563
+ name: "requirements-writer",
25564
+ description: "Requirements writer agent bundle. Authors formal requirement documents from upstream proposals using the 11-category taxonomy (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT), the four-tier classification, and decision-authority rules (direct-write vs. propose-only). Ships 13 templates plus a standards-and-frameworks reference.",
25565
+ appliesWhen: () => true,
25566
+ rules: [
24912
25567
  {
24913
- id: 1,
24914
- prompt: "We had a kickoff meeting where the team decided we need self-service event registration for B2B conferences. The main pain point is that current solutions take months to configure \u2014 operators send spreadsheets and wait weeks for changes. We want organizers to set up registration themselves. Target market is small to mid-size B2B conferences with 500-5000 registrants. Write the BR and FR requirements for this.",
24915
- expected_output: "A BR document in the configured business-requirements directory capturing the business need (self-service registration, reduced configuration time, target market) with stakeholders, success metrics, and scope. One or more FR documents in the functional-requirements directory describing the self-service configuration workflow with user stories, main/alternative/exception flows, and acceptance criteria. All documents follow the category template exactly, carry YAML frontmatter, ship as status Draft, and include traceability links between the BR and FRs. No technology choices are made \u2014 any tech implications are deferred to Proposed ADR or TR documents.",
24916
- files: [],
24917
- product_context_refs: [
24918
- "Mission",
24919
- "In-Scope Capabilities",
24920
- "Out of Scope"
24921
- ]
24922
- },
25568
+ name: "requirements-writer-workflow",
25569
+ description: "Describes the requirements-writer pipeline, the req:write phase label, the four tier:* labels, and the boundary with the upstream requirements-analyst and bcm-writer bundles.",
25570
+ scope: AGENT_RULE_SCOPE.ALWAYS,
25571
+ content: [
25572
+ "# Requirements Writer Workflow",
25573
+ "",
25574
+ "Use `/write-requirement <category> <short-title>` to author one",
25575
+ "formal requirement document. The writer runs in a single phase",
25576
+ "tracked by a GitHub issue labeled `req:write` plus the matching",
25577
+ "`tier:*` label. Issues also carry `type:requirement` (declared",
25578
+ "by the upstream `requirements-analyst` bundle).",
25579
+ "",
25580
+ "The pipeline produces **requirement documents only** \u2014 capability",
25581
+ "models are written by the `bcm-writer` agent and gap discovery is",
25582
+ "the responsibility of the `requirements-analyst` agent. The",
25583
+ "writer never opens `req:scan`, `req:draft-trace`, or `bcm:*`",
25584
+ "issues.",
25585
+ "",
25586
+ "Documents follow the 11-category taxonomy (BR, FR, NFR, TR, ADR,",
25587
+ "SEC, DR, INT, OPS, UX, MT) and the four-tier classification",
25588
+ "(Platform, Industry, Customer Workflow, Consumer Application).",
25589
+ "Templates and a standards-and-frameworks reference ship with the",
25590
+ "skill \u2014 they are the same files for every project that adopts",
25591
+ "the bundle.",
25592
+ "",
25593
+ "Decision-authority rules are non-negotiable: BR / FR / NFR /",
25594
+ "SEC / UX ship as `Status: Draft`; ADR and TR ship as",
25595
+ "`Status: Proposed` with a Recommendation framed for human",
25596
+ "decision; DR / MT / INT / OPS spin technology choices off into",
25597
+ "separate `Proposed` ADR or TR documents.",
25598
+ "",
25599
+ "See the `requirements-writer` agent definition for full workflow",
25600
+ "details, configurable paths, decision-authority rules, and",
25601
+ "phase-by-phase instructions."
25602
+ ].join("\n"),
25603
+ platforms: {
25604
+ cursor: { exclude: true }
25605
+ },
25606
+ tags: ["workflow"]
25607
+ }
25608
+ ],
25609
+ skills: [buildWriteRequirementSkill(paths, issueDefaults)],
25610
+ subAgents: [buildRequirementsWriterSubAgent(paths)],
25611
+ procedures: [nextRequirementIdProcedure],
25612
+ labels: [
24923
25613
  {
24924
- id: 2,
24925
- prompt: "We need to add payment processing to our platform. Registrants should be able to pay with credit cards and we need to support multiple registration tiers with different prices. Write the requirements for this.",
24926
- expected_output: "FR documents covering payment processing (user-visible behavior: selecting a tier, entering payment, receiving confirmation) and registration tiers (configuring tiers, pricing, capacity). The FR documents do NOT select a specific payment provider \u2014 instead, a Proposed ADR is created listing payment provider options (e.g., Stripe, Braintree, Adyen) with pros/cons and a recommendation, with the Decision section set to 'Pending human review'. An INT document describes the integration shape (direction, error handling, SLA expectations) but defers provider selection to the ADR. Open items cross-reference the pending decision.",
24927
- files: [],
24928
- product_context_refs: ["Domain Vocabulary"]
25614
+ name: "req:write",
25615
+ color: "FEF2C0",
25616
+ description: "Phase: write a formal requirement document using the requirements-writer skill"
24929
25617
  },
24930
25618
  {
24931
- id: 3,
24932
- prompt: "Here are notes from our meeting: 'We discussed monitoring. The team agreed we need 99.9% uptime SLA, p99 latency under 200ms for registration API calls, and real-time alerting when error rates spike above 1%. Alice suggested Datadog, Bob preferred CloudWatch since we're already on AWS. No decision was made on tooling.' Write the requirements.",
24933
- expected_output: "An NFR document with measurable targets (99.9% uptime, p99 < 200ms, error rate alerting threshold at 1%) \u2014 written directly as Draft. An OPS document describing monitoring and alerting requirements (what needs monitoring, alerting rules, incident response) \u2014 written directly for the requirements portion. A Proposed ADR or TR for the monitoring tooling decision, listing Datadog and CloudWatch as options with pros/cons, noting each team member's preference, and recommending one with rationale. The ADR/TR has status Proposed with an open item flagging human decision required. The OPS document references the pending tooling decision in its open items.",
24934
- files: [],
24935
- product_context_refs: ["Domain Vocabulary"]
24936
- }
24937
- ]
24938
- },
24939
- null,
24940
- 2
24941
- );
24942
- function buildRequirementsWriterReferenceFiles(paths) {
24943
- return [
24944
- {
24945
- path: "_references/templates/_template-BR.md",
24946
- content: templateBr(paths)
24947
- },
24948
- {
24949
- path: "_references/templates/_template-FR.md",
24950
- content: templateFr(paths)
24951
- },
24952
- {
24953
- path: "_references/templates/_template-NFR.md",
24954
- content: templateNfr(paths)
24955
- },
24956
- {
24957
- path: "_references/templates/_template-TR.md",
24958
- content: templateTr(paths)
24959
- },
24960
- {
24961
- path: "_references/templates/_template-ADR.md",
24962
- content: templateAdr(paths)
24963
- },
24964
- {
24965
- path: "_references/templates/_template-SEC.md",
24966
- content: templateSec(paths)
24967
- },
24968
- {
24969
- path: "_references/templates/_template-DR.md",
24970
- content: templateDr(paths)
24971
- },
24972
- {
24973
- path: "_references/templates/_template-INT.md",
24974
- content: templateInt(paths)
24975
- },
24976
- {
24977
- path: "_references/templates/_template-OPS.md",
24978
- content: templateOps(paths)
24979
- },
24980
- {
24981
- path: "_references/templates/_template-UX.md",
24982
- content: templateUx(paths)
24983
- },
24984
- {
24985
- path: "_references/templates/_template-MT.md",
24986
- content: templateMt(paths)
24987
- },
24988
- {
24989
- path: "_references/templates/_template-category-README.md",
24990
- content: TEMPLATE_CATEGORY_README
24991
- },
24992
- {
24993
- path: "_references/templates/_template-requirements-README.md",
24994
- content: templateRequirementsReadme(paths)
24995
- },
24996
- {
24997
- path: "_references/standards-and-frameworks.md",
24998
- content: STANDARDS_AND_FRAMEWORKS
24999
- },
25000
- {
25001
- path: "evals/evals.json",
25002
- content: WRITE_REQUIREMENT_EVALS_JSON
25003
- }
25004
- ];
25619
+ name: "tier:platform",
25620
+ color: "EDEDED",
25621
+ description: "Architectural tier: core platform (shared infrastructure, APIs, auth, tenant isolation)"
25622
+ },
25623
+ {
25624
+ name: "tier:industry",
25625
+ color: "EDEDED",
25626
+ description: "Architectural tier: industry vertical (capabilities not every tenant needs)"
25627
+ },
25628
+ {
25629
+ name: "tier:customer-workflow",
25630
+ color: "EDEDED",
25631
+ description: "Architectural tier: customer-configured workflow (business logic tenants configure)"
25632
+ },
25633
+ {
25634
+ name: "tier:consumer-app",
25635
+ color: "EDEDED",
25636
+ description: "Architectural tier: consumer application (UI/UX and integrations in external front-ends/systems)"
25637
+ }
25638
+ ]
25639
+ };
25005
25640
  }
25006
- function buildRequirementsWriterSubAgent(paths) {
25641
+ var requirementsWriterBundle = buildRequirementsWriterBundle();
25642
+
25643
+ // src/agent/bundles/requirements-analyst.ts
25644
+ function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
25007
25645
  return {
25008
- name: "requirements-writer",
25009
- description: "Writes formal requirement documents (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) from upstream proposals using the 11-category taxonomy, the four-tier classification, and the decision-authority rules (direct-write vs. propose-only ADR/TR). Handles one req:write issue per session. Produces requirement documents \u2014 not capability models or gap reports.",
25646
+ name: "requirements-analyst",
25647
+ description: "Discovers requirement gaps from BCM model docs, competitive analysis, product docs, and meeting extracts. Produces scan reports, proposals, and req:write issues for the downstream requirements-writer agent. Runs through a 2-phase pipeline (scan \u2192 draft-trace), one phase per session, tracked by req:* GitHub issue labels.",
25010
25648
  model: AGENT_MODEL.POWERFUL,
25011
25649
  maxTurns: 80,
25012
25650
  platforms: { cursor: { exclude: true } },
25013
25651
  prompt: [
25014
- "# Requirements Writer Agent",
25015
- "",
25016
- "You author formal requirement documents using the 11-category",
25017
- "taxonomy (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) and the",
25018
- "four-tier architectural classification (Platform, Industry, Customer",
25019
- "Workflow, Consumer Application). Each session handles exactly **one**",
25020
- "`req:write` issue and writes exactly **one** requirement document.",
25652
+ "# Requirements Analyst Agent",
25021
25653
  "",
25022
- "This agent produces **requirement documents only** \u2014 capability",
25023
- "models are written by the `bcm-writer` agent and requirement-gap",
25024
- "discovery is the responsibility of the `requirements-analyst` agent.",
25025
- "Keep this boundary clean: never open `req:scan`,",
25026
- "`req:draft-trace`, or `bcm:*` issues from this pipeline.",
25654
+ "Dedicated agent loop for discovering requirement gaps from BCM (Business",
25655
+ "Capability Model) documents, product docs, and competitive analysis \u2014 then",
25656
+ "creating well-formed requirement issues for the downstream",
25657
+ "`requirements-writer` agent to draft. Designed for scheduled execution",
25658
+ "downstream of the BCM writer and company research agents.",
25027
25659
  "",
25028
25660
  "Follow your project's shared agent conventions (`AGENTS.md`,",
25029
25661
  "`CLAUDE.md`, or equivalent) for all commit, branch, and PR rules.",
25030
25662
  "",
25031
25663
  "---",
25032
25664
  "",
25033
- ...PROJECT_CONTEXT_READER_SECTION,
25665
+ ...PROJECT_CONTEXT_MAINTAINER_SECTION,
25034
25666
  "## Design Principles",
25035
25667
  "",
25036
- "1. **One requirement per session.** Each `req:write` issue maps to a",
25037
- " single requirement document. Never write two documents in one",
25038
- " session and never start a second issue.",
25039
- "2. **Templates are authoritative.** Every category has a template",
25040
- " under `<TEMPLATES_ROOT>`. Use it verbatim \u2014 do not invent new",
25041
- " sections or reorder existing ones. A section that does not apply",
25042
- " gets `Not applicable \u2014 <reason>` rather than being omitted.",
25043
- "3. **Decision authority is non-negotiable.** Direct-write categories",
25044
- " ship as `Status: Draft`. ADR and TR documents ship as",
25045
- " `Status: Proposed` with a Recommendation that frames a human",
25046
- " decision \u2014 never decide for the human.",
25047
- "4. **Trace upstream.** Every requirement links back to the proposal",
25048
- " that produced it, the source document(s) cited in that proposal,",
25049
- " and the upstream BCM capability or business need it serves.",
25050
- "5. **Cite, don't invent.** When the proposal does not supply a",
25051
- " stakeholder, metric, threat model entry, or technology option,",
25052
- " write `TODO:` and flag the issue with `status:needs-attention`",
25053
- " rather than fabricating content.",
25054
- "",
25055
- "---",
25056
- "",
25057
- "## Configurable Paths",
25058
- "",
25059
- "The pipeline uses these placeholders. Consuming projects override the",
25060
- "defaults by passing paths in the `/write-requirement` skill",
25061
- `invocation, by recording overrides in \`${paths.docsRoot}/project-context.md\`, or`,
25062
- "by extending this rule in their own `agentConfig.rules`.",
25063
- "",
25064
- "| Placeholder | Meaning | Default |",
25065
- "|-------------|---------|---------|",
25066
- `| \`<REQUIREMENTS_ROOT>\` | Root folder for final requirement documents | \`${paths.requirementsRoot}/\` |`,
25067
- `| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Where the upstream \`requirements-analyst\` writes proposals | \`${paths.researchRequirementsRoot}/\` |`,
25068
- `| \`<TEMPLATES_ROOT>\` | Where the category templates ship (set automatically by this bundle) | \`${REQUIREMENTS_WRITER_PATHS.templatesRoot}\` |`,
25069
- `| \`<STANDARDS_REF>\` | Standards & frameworks reference document | \`${REQUIREMENTS_WRITER_PATHS.standardsRef}\` |`,
25070
- `| \`<PREFIX>\` | Project-specific requirement ID prefix | none \u2014 requirement IDs use the category prefix (\`BR\`, \`FR\`, \u2026) unless \`${paths.docsRoot}/project-context.md\` defines a project prefix |`,
25071
- "",
25072
- `If \`${paths.docsRoot}/project-context.md\` specifies a different requirements tree,`,
25073
- "prefer that. Otherwise fall back to the defaults above.",
25074
- "",
25075
- "---",
25076
- "",
25077
- ...REQUIREMENTS_TAXONOMY_TABLE_SECTION,
25078
- "",
25079
- "### Disambiguation Rules",
25080
- "",
25081
- "Apply these rules when deciding where a requirement belongs:",
25082
- "",
25083
- ...REQUIREMENTS_TAXONOMY_DISAMBIGUATION_SECTION,
25084
- "",
25085
- "---",
25086
- "",
25087
- "## Architectural Tiers",
25088
- "",
25089
- "Every requirement also belongs to one of four architectural tiers.",
25090
- 'Tier is **orthogonal** to category \u2014 category answers "what kind"',
25091
- 'while tier answers "where in the architecture." The tier names below',
25092
- "are the bundle defaults; consuming projects may rename or reduce the",
25093
- `tier set in their own \`${paths.docsRoot}/project-context.md\`.`,
25094
- "",
25095
- ...REQUIREMENTS_TIER_TABLE_SECTION,
25096
- "",
25097
- "Tier is a **required metadata field**. Set it in the Metadata table",
25098
- "of every requirement document and apply the matching `tier:*` issue",
25099
- "label (`tier:platform`, `tier:industry`, `tier:customer-workflow`,",
25100
- "or `tier:consumer-app`).",
25101
- "",
25102
- "**Customer field (optional).** When the proposal originated from or",
25103
- "applies to a specific customer, set the `Customer` field in the",
25104
- "Metadata table. Optional for Platform/Industry tiers, expected for",
25105
- "Customer Workflow / Consumer Application tiers when the project",
25106
- "tracks customer profiles.",
25107
- "",
25108
- "**Implementor field (tier-specific).** For Customer Workflow and",
25109
- "Consumer Application tiers, set the `Implementor` field (Consortium",
25110
- "Member / Customer / TBD) when the project tracks that distinction.",
25111
- "Skip the field entirely if it does not apply to your project.",
25112
- "",
25113
- "**Cross-tier traceability.** Higher-tier requirements must link to",
25114
- "lower-tier requirements they depend on using `Depends on (cross-tier)`",
25115
- "links in the Traceability section. For example, a Consumer",
25116
- "Application FR for an intake portal must trace to the Platform INT",
25117
- "for the API it consumes.",
25118
- "",
25119
- "---",
25120
- "",
25121
- "## Decision Authority Rules",
25122
- "",
25123
- "**This is the most important section.** Not all categories are",
25124
- "treated equally. The core principle is: **requirements that describe",
25125
- "*what* and *why* get written directly; decisions about *how* and",
25126
- "*with what* are deferred to humans.**",
25127
- "",
25128
- "### Write Directly (status: `Draft`)",
25129
- "",
25130
- "These categories describe business needs, user behavior, quality",
25131
- "targets, security posture, and user experience. The writer has full",
25132
- "authority to produce these documents:",
25133
- "",
25134
- "- **BR** \u2014 Business Requirements",
25135
- "- **FR** \u2014 Functional Requirements",
25136
- "- **NFR** \u2014 Non-Functional Requirements",
25137
- "- **SEC** \u2014 Security & Compliance",
25138
- "- **UX** \u2014 UX Requirements",
25139
- "",
25140
- "### Write with Partial Deferral",
25141
- "",
25142
- "These categories contain a mix of *what* (write directly) and *how*",
25143
- "(defer). Write the requirement portions directly but defer",
25144
- "technology/tooling selections:",
25145
- "",
25146
- "- **DR** \u2014 Write data models, retention policies, and classification",
25147
- " directly. When backup strategy or storage technology must be",
25148
- " specified, create a `Proposed` ADR or TR instead of choosing.",
25149
- "- **MT** \u2014 Write tenant isolation requirements and metering",
25150
- " dimensions directly. When the isolation *model* must be chosen",
25151
- " (row-level vs schema-per-tenant vs database-per-tenant), create a",
25152
- " `Proposed` ADR instead of choosing.",
25153
- "- **INT** \u2014 Write the integration shape, direction, error handling",
25154
- " strategy, and SLA expectations directly. When the specific",
25155
- " provider must be selected, create a `Proposed` TR/ADR instead.",
25156
- "- **OPS** \u2014 Write operational requirements (monitoring needs,",
25157
- " incident response, deployment strategy) directly. When specific",
25158
- " tooling must be selected, create a `Proposed` TR/ADR instead.",
25159
- "",
25160
- "### Propose Only (status: `Proposed`, pending human decision)",
25161
- "",
25162
- "These categories represent technology and architecture decisions",
25163
- "that humans must make. Produce complete documents with all the",
25164
- "information needed for the decision, but **do not make the decision",
25165
- "itself**.",
25166
- "",
25167
- "- **ADR** \u2014 Write the full document following the ADR template:",
25168
- " 1. **Context** \u2014 forces at play, constraints, what prompted the",
25169
- " decision",
25170
- ' 2. **Decision** \u2014 set to: *"Pending human review. See',
25171
- ' Recommendation below."*',
25172
- " 3. **Alternatives Considered** \u2014 every viable option with",
25173
- " equal-depth analysis. Each gets a description, pros, and cons.",
25174
- " Do not shortchange options you don't prefer.",
25175
- " 4. **Recommendation** \u2014 state which option you recommend, *why*",
25176
- " it's the best fit for the context, and what trade-offs are",
25177
- " accepted. The reasoning must be specific to this project's",
25178
- " situation \u2014 not generic.",
25179
- " 5. **Consequences** \u2014 positive, negative, and neutral consequences",
25180
- " of the recommended option",
25181
- " 6. Set status to `Proposed`. Add an open item flagging that a",
25182
- " human decision is required before dependent requirements can",
25183
- " proceed.",
25184
- "",
25185
- "- **TR** \u2014 Write the full document following the TR template:",
25186
- ' 1. **Technology Choice** \u2014 set to: *"Pending human review. See',
25187
- ' Recommendation below."*',
25188
- " 2. **Alternatives Considered** \u2014 every viable technology option",
25189
- " with description, pros, cons, license, and maturity assessment",
25190
- " for each",
25191
- " 3. **Recommendation** \u2014 state which technology you recommend,",
25192
- " *why*, and what trade-offs are accepted",
25193
- ' 4. **Rationale** \u2014 set to: *"See Recommendation above. Awaiting',
25194
- ' human decision."*',
25195
- " 5. Set status to `Proposed`. Add an open item flagging that a",
25196
- " human decision is required.",
25197
- "",
25198
- "### How Deferral Works in Practice",
25199
- "",
25200
- "When writing a requirement that implies a technology choice:",
25201
- "",
25202
- "1. Write the requirement itself (FR, DR, INT, OPS, etc.) with full",
25203
- " detail.",
25204
- "2. Where the requirement needs a technology decision, add a note:",
25205
- ` *"Technology selection pending \u2014 see [ADR-NNN](../${paths.requirementCategoryDirs.architecturalDecisions}/ADR-NNN-slug.md)"*.`,
25206
- "3. Create the corresponding ADR or TR as `Proposed` with options,",
25207
- " pros/cons, and recommendation.",
25208
- '4. In the ADR/TR open items, add: *"Human decision required.',
25209
- ' Dependent requirements: [list]"*.',
25210
- "5. In the original requirement's open items, add a cross-reference:",
25211
- ` *"Blocked by [ADR-NNN](../${paths.requirementCategoryDirs.architecturalDecisions}/ADR-NNN-slug.md) \u2014`,
25212
- ' technology selection pending human review."*',
25213
- "",
25214
- "This creates a clear chain: the requirement is understood, the",
25215
- "decision is framed with all necessary information, and the",
25216
- "dependency is tracked in both directions.",
25217
- "",
25218
- "---",
25219
- "",
25220
- "## File Naming Convention",
25221
- "",
25222
- "Every requirement file follows this pattern:",
25223
- "",
25224
- "```",
25225
- "{PREFIX}-{NNN}-{slug}.md",
25226
- "```",
25227
- "",
25228
- "- **PREFIX** \u2014 category abbreviation (`BR`, `FR`, `NFR`, `TR`,",
25229
- " `ADR`, `SEC`, `DR`, `INT`, `OPS`, `UX`, `MT`). If the project",
25230
- ` declares a project-wide prefix in \`${paths.docsRoot}/project-context.md\`, use`,
25231
- " it instead.",
25232
- "- **NNN** \u2014 three-digit sequential number (001, 002, 012). Always",
25233
- " check the target category directory under `<REQUIREMENTS_ROOT>`",
25234
- " for the next available number before writing.",
25235
- "- **slug** \u2014 lowercase kebab-case descriptive name.",
25236
- "",
25237
- "Examples: `FR-001-user-registration.md`,",
25238
- "`ADR-003-database-selection.md`,",
25239
- "`SEC-001-authentication-framework.md`.",
25668
+ "1. **Discover, don't write.** This agent identifies *what requirements are",
25669
+ " missing*. The `requirements-writer` agent writes the actual documents.",
25670
+ " The boundary keeps this agent fast and the `requirements-writer`",
25671
+ " authoritative.",
25672
+ "2. **Trace everything.** Every discovered gap links to the source that",
25673
+ " revealed it (a BCM model doc, competitive analysis, product doc, or",
25674
+ " meeting extract).",
25675
+ "3. **Respect the taxonomy.** Route every discovered requirement to the",
25676
+ " correct BCM category (FR, BR, NFR, SEC, DR, INT, OPS, UX, MT, ADR, TR)",
25677
+ " using the shared disambiguation rules \u2014 the same taxonomy the",
25678
+ " `requirements-writer` and `requirements-reviewer` load. The",
25679
+ " canonical source lives in `requirements-taxonomy.ts` and is",
25680
+ " embedded verbatim in both downstream agents' prompts.",
25681
+ "4. **Deduplicate.** Before creating an issue, check whether a requirement",
25682
+ " already exists or an issue is already open for it.",
25240
25683
  "",
25241
25684
  "---",
25242
25685
  "",
25243
- "## Frontmatter",
25244
- "",
25245
- "Every `.md` requirement file must begin with a YAML frontmatter",
25246
- "block. The minimum required fields are `title` and `description`:",
25686
+ "## State Machine Overview",
25247
25687
  "",
25248
- "```markdown",
25249
- "---",
25250
- 'title: "FR-001: User Registration"',
25251
- 'description: "Functional requirement for the user registration workflow."',
25252
- "tier: platform",
25253
- "---",
25688
+ "Requirements synthesis flows through **2 phases**:",
25254
25689
  "",
25255
- "...",
25690
+ "```",
25691
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
25692
+ "\u2502 1. SCAN \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 2. DRAFT-TRACE \u2502",
25693
+ "\u2502 Read docs, \u2502 \u2502 Write proposals,\u2502",
25694
+ "\u2502 identify \u2502 \u2502 create req:write\u2502",
25695
+ "\u2502 gaps, check \u2502 \u2502 issues, update \u2502",
25696
+ "\u2502 for dupes \u2502 \u2502 source docs \u2502",
25697
+ "\u2502 \u2502 \u2502 with traceability\u2502",
25698
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
25256
25699
  "```",
25257
25700
  "",
25258
- "Titles containing colons must be wrapped in double quotes. The",
25259
- "`tier` field must be one of `platform`, `industry`,",
25260
- "`customer-workflow`, or `consumer-app` (use whatever tier slugs the",
25261
- "project declares).",
25262
- "",
25263
- "### Optional traceability extensions",
25701
+ "This pipeline matches the `scan \u2192 draft-trace \u2192 write` pattern",
25702
+ "used by the sibling openhi project. Draft and Trace were previously",
25703
+ "separate phases; they were collapsed because the proposals file Draft",
25704
+ "wrote was only ever consumed by Trace (no human review, no async work",
25705
+ "between the two), so the phase boundary added latency without value.",
25706
+ "See ADR-009 for the full reasoning.",
25264
25707
  "",
25265
- "Projects that publish requirements through Starlight, Astro, or a",
25266
- "similar static-site generator may add structured traceability fields",
25267
- "to frontmatter. One example: a `referencedIn.meetings[]` block that",
25268
- "links the requirement back to a meeting transcript that informed it.",
25269
- "These conventions are **optional** \u2014 adopt them only if your project",
25270
- "already maintains the matching reverse-link structures.",
25708
+ "**Issue labels encode the phase:**",
25271
25709
  "",
25272
- "---",
25710
+ "| Label | Phase | Session work |",
25711
+ "|-------|-------|-------------|",
25712
+ "| `req:scan` | 1. Scan | Read source docs, identify potential requirement gaps, check against existing requirements and open issues, write deduplicated scan report |",
25713
+ "| `req:draft-trace` | 2. Draft & Trace | Write proposals, create `req:write` GitHub issues for each proposal, and update source documents with traceability notes \u2014 all in a single session |",
25273
25714
  "",
25274
- "## Status Lifecycle",
25715
+ "All issues also carry `type:requirement` and a `status:*` label.",
25275
25716
  "",
25276
- "Every requirement has a status that tracks where it is in its",
25277
- "lifecycle:",
25717
+ "**Issue count per scan cycle:** 1 scan + 1 draft-trace = **2 sessions**.",
25278
25718
  "",
25279
- "| Status | Meaning |",
25280
- "|---|---|",
25281
- "| `Draft` | Initial capture, under discussion |",
25282
- "| `Proposed` | Formally proposed, awaiting review \u2014 **used for ADR and TR documents pending human decision** |",
25283
- "| `Accepted` | Approved and active |",
25284
- "| `Implemented` | Fully delivered |",
25285
- "| `Deprecated` | No longer applicable |",
25286
- "| `Superseded` | Replaced by another requirement (link to successor) |",
25719
+ "**Shortened paths:**",
25720
+ "- No gaps found after scan \u2192 skip draft-trace \u2192 **1 session**",
25287
25721
  "",
25288
25722
  "---",
25289
25723
  "",
25290
- "## Traceability",
25291
- "",
25292
- "Every requirement document must include a `## Traceability` section.",
25293
- "Use relative markdown links with the requirement ID as link text.",
25724
+ "## Configurable Paths",
25294
25725
  "",
25295
- "```markdown",
25296
- "## Traceability",
25726
+ "Projects adopting this bundle must define these paths in their agent",
25727
+ "configuration (`agentConfig.rules` extension or project-level docs):",
25297
25728
  "",
25298
- `- **Implements:** [BR-001](../${paths.requirementCategoryDirs.business}/BR-001-self-service-onboarding.md)`,
25299
- `- **Constrained by:** [NFR-001](../${paths.requirementCategoryDirs.nonFunctional}/NFR-001-api-response-times.md)`,
25300
- `- **Related:** [SEC-001](../${paths.requirementCategoryDirs.security}/SEC-001-authentication-framework.md)`,
25301
- "```",
25729
+ "| Placeholder | Meaning | Typical value |",
25730
+ "|-------------|---------|---------------|",
25731
+ `| \`<BCM_DOCS_ROOT>\` | Root of BCM model docs (capability models) | \`${paths.bcmRoot}/\` |`,
25732
+ `| \`<COMPETITIVE_ROOT>\` | Competitive analysis docs | \`${paths.docsRoot}/business-strategy/competitive/\` |`,
25733
+ `| \`<PRODUCT_ROOT>\` | Product roadmap / entity taxonomy | \`${paths.docsRoot}/product/\` |`,
25734
+ `| \`<MEETINGS_ROOT>\` | Meeting extracts | \`${paths.docsRoot}/research/meetings/\` |`,
25735
+ `| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Scan reports and proposals | \`${paths.researchRequirementsRoot}/\` |`,
25736
+ `| \`<REQUIREMENTS_ROOT>\` | Final requirement documents (owned by requirements-writer) | \`${paths.requirementsRoot}/\` |`,
25737
+ "| `<PREFIX>` | Project-specific requirement ID prefix | e.g. `VRTX`, `ACME` |",
25302
25738
  "",
25303
- "### Link Types",
25739
+ "If your project stores these in different locations, substitute accordingly",
25740
+ "wherever the phase instructions reference a path.",
25304
25741
  "",
25305
- "| Link Type | Meaning |",
25306
- "|---|---|",
25307
- "| **Implements** | This requirement realizes a higher-level requirement |",
25308
- "| **Constrained by** | This requirement is bounded by another requirement |",
25309
- "| **Supports** | This requirement contributes to but does not fully realize another |",
25310
- "| **Supersedes** | This requirement replaces a deprecated one |",
25311
- "| **Related** | Informational relationship |",
25742
+ "---",
25312
25743
  "",
25313
- "### Dependency Flow",
25744
+ "## Agent Loop",
25314
25745
  "",
25315
- "```",
25316
- "Business Requirements (BR)",
25317
- " -> justify epics and initiatives",
25746
+ "Run this loop exactly once per session. Never start a second issue.",
25318
25747
  "",
25319
- "Functional (FR), Integration (INT), Multi-Tenancy (MT)",
25320
- " -> decompose into feature work",
25748
+ "1. Claim one open `type:requirement` issue using phase priority:",
25749
+ " `req:scan` > `req:draft-trace`.",
25750
+ "2. Transition `status:ready` \u2192 `status:in-progress` and create the branch",
25751
+ " per your project's branch-naming convention.",
25752
+ "3. Execute the phase handler that matches the issue's `req:*` label.",
25753
+ "4. Commit, push, open a PR (if applicable), and close the issue per your",
25754
+ " project's PR workflow.",
25321
25755
  "",
25322
- "NFRs, Security (SEC), UX",
25323
- " -> appear as acceptance criteria AND as standalone requirements",
25756
+ "---",
25324
25757
  "",
25325
- "Architectural Decisions (ADR)",
25326
- " -> spawn implementation tasks and serve as context for TRs",
25758
+ "## Phase 1: Scan (`req:scan`)",
25327
25759
  "",
25328
- "Technical (TR), Data (DR), Operational (OPS)",
25329
- " -> drive infrastructure and platform work",
25330
- "```",
25760
+ "**Goal:** Read source documents, identify where requirements are missing,",
25761
+ "incomplete, or contradictory, then check each potential gap against existing",
25762
+ "requirements and open issues to eliminate duplicates. Produces a single",
25763
+ "deduplicated scan report.",
25331
25764
  "",
25332
- "---",
25765
+ "**Budget:** Reading source docs + reading requirement registries + searching",
25766
+ "issues. Write one deduplicated scan output file.",
25333
25767
  "",
25334
- "## Open Items",
25768
+ "### Scan Sources",
25335
25769
  "",
25336
- "Every requirement document must end with a `## Open Items` section",
25337
- "(placed just above `## Revision History`). This section surfaces",
25338
- "things that need human attention.",
25770
+ "The issue specifies which source(s) to scan. Common scan scopes:",
25339
25771
  "",
25340
- "### Priority Levels",
25772
+ "| Scope | What to read | What to look for |",
25773
+ "|-------|-------------|-----------------|",
25774
+ `| **BCM model doc** | One \`{PREFIX}-NNN\` doc under \`<BCM_DOCS_ROOT>\` | The doc's project-relevance section (commonly \`## <Project> Relevance\` or \`## Strategic Implications\`) \u2014 gaps where capabilities exist but no FR/BR/INT addresses them. Use \`${paths.docsRoot}/project-context.md\` to judge what is relevant. |`,
25775
+ "| **Competitive analysis** | One `comp-*.md` doc under `<COMPETITIVE_ROOT>` | Feature comparison gaps \u2014 competitor features the product lacks requirements for |",
25776
+ "| **Product roadmap** | `<PRODUCT_ROOT>/prioritized-feature-roadmap.md` | Roadmap items without corresponding FRs |",
25777
+ "| **Entity taxonomy** | `<PRODUCT_ROOT>/entity-taxonomy.md` | Entities without CRUD requirements (FR), data requirements (DR), or security requirements (SEC) |",
25778
+ "| **Meeting extract** | `<MEETINGS_ROOT>/meeting-*.extract.md` | Requirements identified but not yet formalized |",
25341
25779
  "",
25342
- "| Priority | Meaning |",
25343
- "|---|---|",
25344
- "| **P0 \u2014 Blocker** | Cannot proceed with implementation until resolved |",
25345
- "| **P1 \u2014 High** | Significantly affects scope, security, or architecture |",
25346
- "| **P2 \u2014 Medium** | Affects quality or completeness but doesn't block progress |",
25347
- "| **P3 \u2014 Low** | Minor clarification, can be resolved asynchronously |",
25780
+ "### Steps",
25348
25781
  "",
25349
- "### Subsection Types",
25782
+ "1. **Read the source documents** specified in the issue.",
25350
25783
  "",
25351
- "- **Identified Gaps** \u2014 Missing functionality, unhandled edge cases,",
25352
- " incomplete aspects",
25353
- "- **Follow-up Questions** \u2014 Ambiguities where intent cannot be",
25354
- " confidently inferred",
25355
- "- **Contradictions & Inconsistencies** \u2014 Conflicts between",
25356
- " requirements, documentation, or stated goals",
25784
+ "2. **Identify potential gaps.** For each potential missing requirement:",
25785
+ " - Classify into the correct BCM category (FR, BR, NFR, SEC, DR, INT,",
25786
+ " OPS, UX, MT, ADR, TR)",
25787
+ " - Apply the shared disambiguation rules (from `requirements-taxonomy.ts`,",
25788
+ " also embedded in the requirements-writer and requirements-reviewer",
25789
+ " prompts)",
25790
+ " - Note the source that revealed the gap",
25791
+ " - Estimate priority based on the source context",
25357
25792
  "",
25358
- "Number each item within its subsection. Assign a priority. Call out",
25359
- "interdependencies with open items in other documents using the",
25360
- `format: \`-> Depends on: [FR-001 Open Item #2](../${paths.requirementCategoryDirs.functional}/FR-001-slug.md#open-items)\`.`,
25793
+ "3. **Read the requirements registry.** Scan `_index.md` files in each",
25794
+ " `<REQUIREMENTS_ROOT>/<category>/` directory to know what already exists.",
25361
25795
  "",
25362
- "The `## Open Items` section is **not optional** \u2014 it appears in every",
25363
- 'document, even if a subsection is empty (write "None identified.").',
25796
+ "4. **Search for existing issues.** For each potential gap, search open issues:",
25797
+ " ```bash",
25798
+ ' gh issue list --label "type:requirement" --state open \\',
25799
+ " --json number,title --limit 100",
25800
+ " ```",
25364
25801
  "",
25365
- "---",
25802
+ "5. **Classify each gap:**",
25803
+ " - **New** \u2014 no existing requirement or open issue covers this",
25804
+ " - **Duplicate** \u2014 an existing requirement already addresses this",
25805
+ " - **In progress** \u2014 an open issue already targets this",
25806
+ " - **Partial** \u2014 existing requirement partially covers this; note the gap",
25366
25807
  "",
25367
- "## Reading the Templates and Standards Reference",
25808
+ "6. **Write the deduplicated scan report** to:",
25809
+ " ```",
25810
+ " <RESEARCH_REQUIREMENTS_ROOT>/req-scan-<scope>-<YYYY-MM-DD>.md",
25811
+ " ```",
25368
25812
  "",
25369
- "Before writing any requirement document:",
25813
+ " Format:",
25814
+ " ```markdown",
25815
+ " ---",
25816
+ ' title: "Requirements Scan: <scope>"',
25817
+ " date: YYYY-MM-DD",
25818
+ " parent_issue: <N>",
25819
+ " status: complete",
25820
+ " ---",
25370
25821
  "",
25371
- "1. **Read the matching template** under `<TEMPLATES_ROOT>` for the",
25372
- " category specified in the issue (`_template-FR.md`,",
25373
- " `_template-ADR.md`, etc.). Templates are named",
25374
- " `_template-{PREFIX}.md`. Every section in the template must",
25375
- " appear in the final document.",
25822
+ " ## Source Documents Reviewed",
25823
+ " - <path> \u2014 <brief description>",
25376
25824
  "",
25377
- "2. **Read `<STANDARDS_REF>`** if the category requires it (especially",
25378
- " SEC, NFR, INT, ADR, TR). The reference covers OWASP ASVS, ISO",
25379
- " 25010, BABOK, MADR, Enterprise Integration Patterns, WCAG, and",
25380
- " related standards that ground the templates.",
25825
+ " ## Existing Requirements Checked",
25826
+ " - <category>: <count> existing docs, <count> open issues",
25381
25827
  "",
25382
- "Templates and the standards reference ship with this skill \u2014 they",
25383
- "are the same files for every project that adopts the bundle.",
25828
+ " ## Identified Gaps (New)",
25829
+ " ### Gap 1: <Title>",
25830
+ " - **Category:** FR / BR / NFR / SEC / DR / INT / OPS / UX / MT / ADR / TR",
25831
+ " - **Source:** <path to doc + section that revealed this gap>",
25832
+ " - **Priority:** High / Normal / Low",
25833
+ " - **Rationale:** <why this requirement is needed>",
25834
+ " - **Duplicate check:** No existing requirement or open issue found",
25835
+ " - **Proposed scope:** <1-2 sentences on what the requirement should cover>",
25384
25836
  "",
25385
- "---",
25837
+ " ## Already Covered",
25838
+ " <list of potential gaps that turned out to already have requirements>",
25386
25839
  "",
25387
- "## Maintaining the Registry Index",
25840
+ " ## In Progress",
25841
+ " <gaps that already have open issues \u2014 include issue numbers>",
25388
25842
  "",
25389
- "Each category directory contains a `README.md` (or `_index.md`,",
25390
- "depending on project convention) that lists every requirement in",
25391
- "that category. **After writing any new requirement document, update",
25392
- "the category index:**",
25843
+ " ## Ambiguous / Needs Human Decision",
25844
+ " <gaps where the correct category or scope is unclear>",
25845
+ " ```",
25393
25846
  "",
25394
- "1. Read the index file in the same directory as the new requirement.",
25395
- "2. Add a row to the requirements table with: ID (linked to the",
25396
- " file), Title, Status, Priority, Last Updated date.",
25397
- "3. Insert the row in sequence-number order.",
25398
- "4. If the index contains a placeholder message, remove it.",
25847
+ "7. **Create downstream issues based on findings:**",
25848
+ " - If any new gaps were identified \u2192 create `req:draft-trace` issue",
25849
+ " (blocked on this issue via `Depends on: #N`).",
25850
+ " - If **no gaps** were found \u2192 stop (no further phases needed). Comment",
25851
+ " on the issue noting that no gaps were identified, and proceed directly",
25852
+ " to commit and push. The scan issue will be marked done with no",
25853
+ " downstream work needed.",
25399
25854
  "",
25400
- "If the project has not seeded category READMEs yet, generate one",
25401
- "from `_template-category-README.md` (shipped with this skill) and",
25402
- "commit it alongside the requirement document.",
25855
+ "8. **Commit and push.**",
25403
25856
  "",
25404
25857
  "---",
25405
25858
  "",
25406
- "## Agent Loop",
25859
+ "## Phase 2: Draft & Trace (`req:draft-trace`)",
25407
25860
  "",
25408
- "Run this loop exactly once per session. Never start a second issue.",
25861
+ "**Goal:** Expand each identified gap into a requirement proposal, create",
25862
+ "a `req:write` GitHub issue for each proposal so the downstream",
25863
+ "`requirements-writer` bundle picks it up, and backfill source-document",
25864
+ "traceability notes \u2014 all in a single session.",
25409
25865
  "",
25410
- "1. Claim one open `req:write` issue (also carries `type:requirement`",
25411
- " and a `tier:*` label).",
25412
- "2. Transition `status:ready` \u2192 `status:in-progress` and create the",
25413
- " branch per your project's branch-naming convention.",
25414
- "3. Execute the write phase below.",
25415
- "4. Commit, push, open a PR, and close the issue per your project's",
25416
- " PR workflow. Closing the PR transitions the issue to",
25417
- " `status:done` per the standard label conventions.",
25866
+ "**Budget:** No web searches. Reading + writing proposals + issue creation",
25867
+ "+ minor traceability edits to source documents.",
25418
25868
  "",
25419
- "---",
25869
+ "Draft and Trace were previously separate phases. They were collapsed",
25870
+ "because the proposals file written by Draft was only ever consumed by",
25871
+ "Trace \u2014 no human review, no async work, no CI validation sat between",
25872
+ "them. See ADR-009 for the decision record.",
25420
25873
  "",
25421
- "## The `req:write` Issue Schema",
25874
+ "### `req:write` issue schema",
25422
25875
  "",
25423
- "This section is the **authoritative schema** for `req:write` issue",
25424
- "bodies. Upstream producers (the `requirements-analyst` trace phase,",
25425
- "the `requirements-reviewer` follow-up generator, and any human",
25426
- "authoring a `req:write` issue directly) must embed or link to this",
25427
- "same schema so the three required fields are always present when",
25428
- "the writer picks the issue up.",
25876
+ "The `req:write` issues this phase creates are picked up by the",
25877
+ "downstream `requirements-writer` agent, which parses a strict",
25878
+ "schema on intake. The authoritative schema is defined in the",
25879
+ "`requirements-writer` sub-agent prompt under **The `req:write`",
25880
+ "Issue Schema** \u2014 every `req:write` issue this phase opens must",
25881
+ "conform. The same schema is embedded in the",
25882
+ "`requirements-reviewer` follow-up generator so the three",
25883
+ "producers never drift apart.",
25429
25884
  "",
25430
25885
  ...REQ_WRITE_ISSUE_SCHEMA_SECTION,
25431
25886
  "",
25432
- "---",
25433
- "",
25434
- "## The `req:write` Phase",
25887
+ "**Deriving the three required fields from the proposal.** For",
25888
+ "every `req:write` issue this phase opens, the three fields come",
25889
+ "directly from the proposal entry written earlier in the same",
25890
+ "session:",
25435
25891
  "",
25436
- "**Goal:** Read the proposal that produced this issue, write a single",
25437
- "requirement document under `<REQUIREMENTS_ROOT>`, and update the",
25438
- "category index.",
25892
+ "- **Category** \u2014 the proposal's `**Category:**` line.",
25893
+ "- **Tier** \u2014 the proposal's `**Tier:**` line.",
25894
+ "- **Output Path** \u2014",
25895
+ " `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`,",
25896
+ " using the sequence number determined in the proposal step below.",
25439
25897
  "",
25440
- "**Budget:** Read the issue body, the named proposal file under",
25441
- "`<RESEARCH_REQUIREMENTS_ROOT>`, the matching category template under",
25442
- "`<TEMPLATES_ROOT>`, and `<STANDARDS_REF>` if the category needs it.",
25443
- "Write one requirement document and one category-index update. No web",
25444
- "searches.",
25898
+ "**Validation step \u2014 required before opening the issue.** Before",
25899
+ "calling `gh issue create`, verify all three required fields are",
25900
+ "populated and internally consistent (Category matches title",
25901
+ "prefix, Tier matches `tier:*` label, Output Path filename starts",
25902
+ "with the Category prefix). If any field cannot be derived, open",
25903
+ "the issue with `status:needs-attention` (not `status:ready`) and",
25904
+ "include a `Missing: <field> \u2014 <one-line reason>` line so a human",
25905
+ "triaging the issue only has to supply the remaining value(s)",
25906
+ "before flipping the label to `status:ready`.",
25445
25907
  "",
25446
25908
  "### Steps",
25447
25909
  "",
25448
- "1. **Parse the issue.** The `req:write` issue body follows the",
25449
- " authoritative schema in the",
25450
- " [The `req:write` Issue Schema](#the-reqwrite-issue-schema)",
25451
- " section below. Before writing, verify every required field",
25452
- " (Category, Tier, Output Path) is present and internally",
25453
- " consistent:",
25910
+ "1. **Read the scan report** from Phase 1.",
25454
25911
  "",
25455
- " - Category matches the prefix on the requirement title and on",
25456
- " the Output Path filename.",
25457
- " - Tier matches the `tier:*` label applied to the issue.",
25458
- " - Output Path sits under",
25459
- " `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`.",
25912
+ "2. **For each gap**, write a detailed proposal:",
25460
25913
  "",
25461
- " If any required field is missing, contradictory, or the issue",
25462
- " already carries `status:needs-attention` with a `Missing:` line",
25463
- " (upstream could not derive it), comment on the issue with",
25464
- " which field is wrong, ensure `status:needs-attention` is set,",
25465
- " and stop without writing. A human triages before the writer",
25466
- " tries again.",
25914
+ " ```markdown",
25915
+ " ## Proposed: <PREFIX>-<NNN> \u2014 <Title>",
25467
25916
  "",
25468
- "2. **Read the proposal.** Open the proposals file referenced in the",
25469
- " issue inputs (under `<RESEARCH_REQUIREMENTS_ROOT>`). Find the",
25470
- " specific proposal entry that matches this requirement's title and",
25471
- " category. Treat the proposal as the authoritative source \u2014 do not",
25472
- " invent fields it omits.",
25917
+ " **Category:** <FR/BR/NFR/SEC/DR/INT/OPS/UX/MT/ADR/TR>",
25918
+ " **Tier:** <platform/industry/customer-workflow/consumer-app>",
25919
+ " **Priority:** <High/Normal/Low>",
25920
+ " **Source:** <document path and section>",
25473
25921
  "",
25474
- "3. **Read the matching template** under `<TEMPLATES_ROOT>` for the",
25475
- " category. Read `<STANDARDS_REF>` if the category is SEC, NFR,",
25476
- " INT, ADR, or TR \u2014 these depend on the standards reference for",
25477
- " correct framing.",
25922
+ " ### Summary",
25923
+ " <2-3 sentences describing what the requirement should capture>",
25478
25924
  "",
25479
- "4. **Pick the next sequence number.** Scan the target category",
25480
- " directory under `<REQUIREMENTS_ROOT>` for existing files matching",
25481
- " `{PREFIX}-NNN-*.md`. Use the next unused three-digit number.",
25925
+ " ### Draft Acceptance Criteria",
25926
+ " - [ ] <testable criterion 1>",
25927
+ " - [ ] <testable criterion 2>",
25928
+ " - [ ] <testable criterion 3>",
25482
25929
  "",
25483
- "5. **Apply the decision-authority rules.** Default to `Status:",
25484
- " Draft` for direct-write categories (BR, FR, NFR, SEC, UX). Set",
25485
- " `Status: Proposed` for ADR and TR documents and frame the",
25486
- " Recommendation section as a human decision. For DR/MT/INT/OPS",
25487
- " categories that imply a technology choice, write the requirement",
25488
- " directly but spin the technology decision off into a separate",
25489
- " `Proposed` ADR or TR \u2014 record the cross-link in the Open Items",
25490
- " section of both documents.",
25930
+ " ### Traceability",
25931
+ " - **Implements:** <BR or parent requirement if applicable>",
25932
+ " - **Related:** list each interacting requirement on its own",
25933
+ " `- <PREFIX>-<NNN>` line in ascending ID order (append-only \u2014",
25934
+ " add your own entries, never rewrite or collapse existing ones",
25935
+ " into a comma-separated line), not a single comma-joined line",
25936
+ " - **Source:** <BCM doc, competitive analysis, or meeting that revealed",
25937
+ " the gap \u2014 use a markdown link. If the source is a meeting note, the",
25938
+ " downstream requirement doc must include the same meeting as a link in",
25939
+ " its Traceability `Related:` list.>",
25491
25940
  "",
25492
- "6. **Write the document.** Fill every section in the template. For",
25493
- " sections the proposal does not supply, write `TODO:` plus a brief",
25494
- " note describing what input is needed, and add a corresponding",
25495
- " `## Open Items` entry. Never leave a template section out.",
25941
+ " ### Decision Authority",
25942
+ ' <"Direct write" for BR/FR/NFR/SEC/UX, or "Proposed \u2014 needs human',
25943
+ ' decision" for ADR/TR, or "Mixed \u2014 defer technology choices" for',
25944
+ " DR/MT/INT/OPS>",
25496
25945
  "",
25497
- "7. **Set frontmatter.** At minimum: `title`, `description`, `tier`.",
25498
- " If the project declares optional frontmatter conventions in",
25499
- ` \`${paths.docsRoot}/project-context.md\` (such as \`referencedIn.meetings[]\`),`,
25500
- " honor them. Otherwise stop at the minimum.",
25946
+ " ### Notes for Requirements Writer",
25947
+ " <any context the writer should know \u2014 related ADRs, existing partial",
25948
+ " coverage, relevant competitive features>",
25949
+ " ```",
25501
25950
  "",
25502
- "8. **Update the category index.** If the category directory has a",
25503
- " `README.md` or `_index.md` registry, add a row for the new",
25504
- " document in sequence order. If no index exists yet, generate one",
25505
- " from `_template-category-README.md`.",
25951
+ "3. **Write the proposals** to:",
25952
+ " ```",
25953
+ " <RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<YYYY-MM-DD>.md",
25954
+ " ```",
25506
25955
  "",
25507
- "9. **Cross-link upstream.** Add `## Traceability` entries pointing",
25508
- " to the proposals file (under `<RESEARCH_REQUIREMENTS_ROOT>`),",
25509
- " the source documents the proposal cited, and any BCM capability",
25510
- " the requirement supports.",
25956
+ "4. **Determine next sequence numbers.** For the **first** proposal",
25957
+ " in a given category this session, run",
25958
+ " `.claude/procedures/next-requirement-id.sh <PREFIX> <category-dir>`",
25959
+ " (no issue number \u2014 the `req:write` issues are not filed yet) to",
25960
+ " get the starting ID from live state (existing docs plus open",
25961
+ " `req:write` issues). For each subsequent proposal in the **same**",
25962
+ " category within this single batch session, increment the ID",
25963
+ " locally (the freshly-filed issues from this batch are not yet",
25964
+ " reflected in the procedure's live-state query). These IDs are",
25965
+ " **hints** stamped into the issue body; the downstream writer",
25966
+ " re-validates and finalizes each ID at write-start, so a residual",
25967
+ " collision costs nothing. If the procedure is unavailable, fall",
25968
+ " back to scanning `<REQUIREMENTS_ROOT>/<category>/` for the next",
25969
+ " available `NNN`.",
25511
25970
  "",
25512
- "10. **Quality checks.** Before committing, verify:",
25513
- " - [ ] Frontmatter has `title` and `description` (and `tier` if",
25514
- " the project uses tier classification)",
25515
- " - [ ] Title matches the `# Heading` line",
25516
- " - [ ] File name follows `{PREFIX}-{NNN}-{slug}.md`",
25517
- " - [ ] Status is `Draft` for direct-write categories or `Proposed`",
25518
- " for ADR/TR",
25519
- " - [ ] Every template section is present (use `TODO:` or `Not",
25520
- " applicable \u2014 <reason>` for unfilled sections)",
25521
- " - [ ] `## Traceability` exists with at least one upstream link",
25522
- " - [ ] No technology decisions made in direct-write categories \u2014",
25523
- " deferred to `Proposed` ADR/TR with cross-links in Open",
25524
- " Items",
25525
- " - [ ] `## Open Items` is present with Identified Gaps,",
25526
- " Follow-up Questions, and Contradictions subsections",
25527
- " - [ ] ADR/TR documents include a Recommendation section and an",
25528
- " Open Item flagging the human decision required",
25529
- " - [ ] Category index updated with a row for the new document",
25530
- " - [ ] Tier value matches the issue's `tier:*` label",
25531
- " - [ ] Cross-tier traceability entries exist where the document",
25532
- " depends on or enables a different tier",
25971
+ "5. **Create requirement issues.** For each proposal, file a",
25972
+ " `req:write` issue using the canonical recipe documented in",
25973
+ " `## Template: req:write` of",
25974
+ " `docs/src/content/docs/agents/issue-templates.md`.",
25533
25975
  "",
25534
- "11. **Commit and push.** Use a `docs(<category>):` conventional",
25535
- " commit message. The PR closes the `req:write` issue.",
25976
+ ` All \`type:requirement\` issues default to \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\` (override`,
25977
+ " only if the proposal's priority was explicitly High or Low). Each",
25978
+ " issue must also carry the `req:write` phase label plus the matching",
25979
+ " `tier:*` label so the downstream `requirements-writer` bundle picks",
25980
+ " it up with the correct tier. Concretely, the label list includes",
25981
+ ' `--label "type:requirement"`, `--label "req:write"`,',
25982
+ ` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
25983
+ ` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
25536
25984
  "",
25537
- "---",
25985
+ " The body must carry the three writer-required fields in an",
25986
+ " `## Objective` block, written as bold-prefixed lines so the",
25987
+ " writer's intake parser can pull them out, plus the reserved-ID",
25988
+ " hint from step 4:",
25538
25989
  "",
25539
- "## Output Boundaries",
25990
+ " - `**Category:** <BR/FR/NFR/TR/ADR/SEC/DR/INT/OPS/UX/MT>`",
25991
+ " - `**Tier:** <platform/industry/customer-workflow/consumer-app>`",
25992
+ " - `**Output Path:** <REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`",
25993
+ " - `**Requirement ID:** <PREFIX>-<NNN>` \u2014 the ID reserved in step 4",
25994
+ " (must match the `<PREFIX>-<NNN>` in the Output Path). This is a",
25995
+ " **hint**: the downstream writer re-validates and finalizes the",
25996
+ " ID at write-start, so a residual collision is repaired there",
25997
+ " for free.",
25540
25998
  "",
25541
- "This agent writes **only** to:",
25999
+ " Then under `## Inputs / Read`, list the proposals file",
26000
+ " (`<RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<date>.md`)",
26001
+ " and any source documents the writer should consult (BCM model",
26002
+ " docs, competitive analyses, product docs).",
25542
26003
  "",
25543
- "- `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md` \u2014 one",
25544
- " requirement document per session",
25545
- "- `<REQUIREMENTS_ROOT>/<category-dir>/README.md` (or `_index.md`) \u2014",
25546
- " category index, one row appended per session",
25547
- "- `<REQUIREMENTS_ROOT>/README.md` (or `_index.md`) \u2014 only if the",
25548
- " top-level requirements README does not yet exist; generate from",
25549
- " `_template-requirements-README.md` and stop",
26004
+ " When one of Category / Tier / Output Path could not be derived",
26005
+ " from the proposal (for example, the proposal omitted the Tier",
26006
+ ` line), replace \`status:${labelsForPhase(issueDefaults, "req:write").status}\` with \`status:needs-attention\` in`,
26007
+ " the label list and add a `Missing: <field> \u2014 <reason>` line",
26008
+ " directly below the Output Path line in the body. Populate",
26009
+ " whichever of the three fields **could** be derived so a human",
26010
+ " triaging the issue has the minimum possible cleanup.",
25550
26011
  "",
25551
- "The pipeline produces **requirement documents**. It does not write",
25552
- "BCM capability models, people profiles, company profiles, software",
25553
- "profiles, scan reports, or proposal files \u2014 those belong to",
25554
- "specialized upstream/downstream agents.",
26012
+ "6. **Update source documents.** In each BCM model doc or competitive",
26013
+ " analysis that was scanned, add a note in the project-relevance /",
26014
+ " strategic-implications section (whichever heading the source doc uses)",
26015
+ " indicating that a requirement issue was created. Each note is its",
26016
+ " own single line:",
25555
26017
  "",
25556
- "**Do NOT create:**",
25557
- "- `req:scan` or `req:draft-trace` issues \u2014 those belong to",
25558
- " the `requirements-analyst` bundle",
25559
- "- `bcm:*` issues \u2014 those belong to the `bcm-writer` bundle",
25560
- "- `people:*`, `company:*`, `software:*`, `research:*`, or",
25561
- " `industry:*` issues \u2014 those belong to their respective bundles",
26018
+ " ```markdown",
26019
+ " - Gap addressed: see [<PREFIX>-<NNN>](<relative path to requirement doc>)",
26020
+ " (issue #<N>)",
26021
+ " ```",
25562
26022
  "",
25563
- "If the proposal surfaces work that needs one of the above, comment",
25564
- "on the `req:write` issue with the suggested follow-up and let a",
25565
- "human route it.",
26023
+ " These `Gap addressed:` notes form an **append-only,",
26024
+ " one-per-line, deterministically sorted** list \u2014 the shared BCM /",
26025
+ " competitive-analysis source docs are edited concurrently by",
26026
+ " sibling `req:draft-trace` sessions, so this list is a",
26027
+ " merge-conflict hotspot. Maintain it under the same discipline",
26028
+ " the `shared-editing-safety` rule prescribes for shared registries,",
26029
+ " applied here to this one list section (the surrounding source doc",
26030
+ " is a hand-authored document, not a registry, so only this list is",
26031
+ " governed):",
26032
+ "",
26033
+ " - **One entry per line.** Add each `- Gap addressed:` note as its",
26034
+ " own new bullet. Never collapse notes into a single",
26035
+ " comma-separated line \u2014 a single mutable line always conflicts",
26036
+ " when two sessions touch it.",
26037
+ " - **Ascending order.** Insert your bullet in the position that",
26038
+ " keeps the list sorted by requirement ID (`<PREFIX>-<NNN>`), so",
26039
+ " concurrent sibling inserts land on different lines and git",
26040
+ " auto-merges them.",
26041
+ " - **Append-only.** Never rewrite, reorder, or merge existing",
26042
+ " notes; add only your own.",
26043
+ " - **Defer the commit of this section to the final pre-push",
26044
+ " step.** Pull the latest default branch before inserting, insert",
26045
+ " only your own note, and if a concurrent sibling already landed a",
26046
+ " bullet, rebase and re-insert. Commit this shared list section on",
26047
+ " its own at the end, then push immediately.",
26048
+ "",
26049
+ "7. **Comment on the scan issue** with a summary of all issues created and",
26050
+ " all docs updated.",
26051
+ "",
26052
+ "8. **Commit and push.**",
25566
26053
  "",
25567
26054
  "---",
25568
26055
  "",
@@ -25570,157 +26057,118 @@ function buildRequirementsWriterSubAgent(paths) {
25570
26057
  "",
25571
26058
  "| Direction | Agent | What |",
25572
26059
  "|-----------|-------|------|",
25573
- "| Upstream | `requirements-analyst` | Discovers gaps, drafts proposals, and creates `req:write` issues that this agent picks up |",
25574
- "| Upstream | `bcm-writer` | Provides BCM capability documents that requirements trace back to via `## Traceability` links |",
25575
- "| Peer | `meeting-analyst` | Provides meeting transcripts that may inform a requirement's traceability extensions (optional) |",
26060
+ `| Upstream | BCM Writer | Scans capability-model docs for project-relevance gaps (judged against \`${paths.docsRoot}/project-context.md\`) |`,
26061
+ "| Upstream | Company Research | Scans competitive analysis for feature comparison gaps |",
26062
+ "| Upstream | Meeting Analyst | Scans meeting extracts for requirement proposals |",
26063
+ "| Downstream | `requirements-writer` | Picks up the `type:requirement` + `req:write` issues this agent creates and drafts the actual requirement document |",
25576
26064
  "",
25577
- "**File boundaries:** Reads `<RESEARCH_REQUIREMENTS_ROOT>` (proposals)",
25578
- "and the source documents the proposals cite. Writes",
25579
- "`<REQUIREMENTS_ROOT>` and the category index files. Never edits",
25580
- "proposals, scan reports, BCM documents, or profiles.",
26065
+ "**File boundaries:** Writes to `<RESEARCH_REQUIREMENTS_ROOT>/req-*.md` and",
26066
+ "minor traceability edits to `<BCM_DOCS_ROOT>` and `<COMPETITIVE_ROOT>`.",
26067
+ "Never writes to `<REQUIREMENTS_ROOT>/` \u2014 that is owned by the",
26068
+ "requirements-writer agent.",
26069
+ "",
26070
+ "---",
26071
+ "",
26072
+ "## Blocked Issues",
26073
+ "",
26074
+ "Additional block reasons specific to requirements synthesis:",
26075
+ "- Source document has unresolved contradictions",
26076
+ "- Category classification is ambiguous (needs human disambiguation)",
26077
+ "- Dependent BCM documents are still in draft with placeholder content",
25581
26078
  "",
25582
26079
  "---",
25583
26080
  "",
25584
26081
  "## Rules",
25585
26082
  "",
25586
- "- **One requirement per session.** Never write two documents in one",
25587
- " session and never start a second issue.",
25588
- "- **Templates are authoritative.** Use the shipped template verbatim",
25589
- " for the category. Every template section must appear in the final",
25590
- " document.",
25591
- "- **Decision authority is non-negotiable.** Direct-write categories",
25592
- " ship as `Draft`. ADR and TR ship as `Proposed` with a Recommendation",
25593
- " framed for human decision. Mixed-deferral categories spin",
25594
- " technology choices off into separate `Proposed` documents.",
25595
- "- **Cite, don't invent.** When the proposal omits a stakeholder,",
25596
- " metric, threat model entry, or technology option, write `TODO:` and",
25597
- " flag the issue with `status:needs-attention`.",
25598
- "- **Trace upstream.** Every requirement links back to its proposal,",
25599
- " the source documents the proposal cited, and the BCM capability",
25600
- " it supports (when applicable).",
25601
- "- **Update the category index every time.** A requirement that",
25602
- " exists as a file but is missing from its category index is",
25603
- " invisible to anyone browsing the documentation tree.",
25604
- "- **Write requirements, not capability models or gap reports.**",
25605
- " Never open `req:scan`, `req:draft-trace`, or `bcm:*` issues from",
25606
- " this pipeline."
26083
+ "- **Discover, don't write requirements.** Create issues for the",
26084
+ " `requirements-writer` agent \u2014 don't write requirement documents",
26085
+ " directly.",
26086
+ "- **Deduplicate rigorously.** Check both existing docs and open issues",
26087
+ " before flagging a gap.",
26088
+ "- **Respect decision authority.** Mark ADR/TR proposals as needing human",
26089
+ " decision. Don't create direct-write issues for technology choices.",
26090
+ "- **Bidirectional traceability.** Every `req-scan-*.md` and",
26091
+ " `req-proposals-*.md` must include a `## Produced` section listing the",
26092
+ " downstream requirement issues (and eventual requirement documents) it",
26093
+ " spawned, as markdown links; each formal requirement document under",
26094
+ " `<REQUIREMENTS_ROOT>/` must include a forward link back to the scan or",
26095
+ " proposal that produced it."
25607
26096
  ].join("\n")
25608
26097
  };
25609
26098
  }
25610
- function buildWriteRequirementSkill(paths, issueDefaults) {
26099
+ function buildScanRequirementsSkill(issueDefaults) {
25611
26100
  return {
25612
- name: WRITE_REQUIREMENT_SKILL_NAME,
25613
- description: "Write one formal requirement document (BR / FR / NFR / TR / ADR / SEC / DR / INT / OPS / UX / MT) using the shipped category template and decision-authority rules. Picks up a req:write issue created by the upstream requirements-analyst pipeline (or kicked off ad hoc) and dispatches the requirements-writer agent.",
26101
+ name: "scan-requirements",
26102
+ description: "Kick off a requirements-analyst scan across BCM model docs, competitive analysis, product docs, or meeting extracts. Creates a req:scan issue and dispatches Phase 1.",
25614
26103
  disableModelInvocation: true,
25615
26104
  userInvocable: true,
25616
26105
  context: "fork",
25617
- agent: "requirements-writer",
26106
+ agent: "requirements-analyst",
25618
26107
  platforms: { cursor: { exclude: true } },
25619
- referenceFiles: buildRequirementsWriterReferenceFiles(paths),
25620
26108
  instructions: [
25621
- "# Write Requirement",
26109
+ "# Scan Requirements",
25622
26110
  "",
25623
- "Write one formal requirement document using the 11-category taxonomy",
25624
- "(BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) and the",
25625
- "decision-authority rules (direct-write vs. propose-only ADR/TR).",
25626
- "Dispatches the `requirements-writer` agent.",
26111
+ "Kick off a requirements-analyst scan cycle. Creates a `req:scan` issue",
26112
+ "targeted at the requested scope and dispatches Phase 1 (Scan) in the",
26113
+ "requirements-analyst agent.",
25627
26114
  "",
25628
26115
  "## Usage",
25629
26116
  "",
25630
- "/write-requirement <category> <short-title>",
25631
- "",
25632
- "Where `<category>` is one of `BR`, `FR`, `NFR`, `TR`, `ADR`, `SEC`,",
25633
- "`DR`, `INT`, `OPS`, `UX`, `MT`.",
25634
- "",
25635
- "Optional extensions in the issue body:",
25636
- "- `tier: platform | industry | customer-workflow | consumer-app` \u2014",
25637
- " the architectural tier (default: `platform`)",
25638
- "- `prefix: <PROJECT_PREFIX>` \u2014 override the default category prefix",
25639
- ` with a project-specific one declared in \`${paths.docsRoot}/project-context.md\``,
25640
- "- `customer: <link-or-slug>` \u2014 link the requirement to a customer",
25641
- " profile (expected for Customer Workflow / Consumer Application",
25642
- " tiers in projects that track customer profiles)",
25643
- "- `proposal: <path>` \u2014 pin the upstream proposal file under",
25644
- " `<RESEARCH_REQUIREMENTS_ROOT>` (default: derived from the issue",
25645
- " context)",
25646
- "- `output: <path>` \u2014 override the default Output Path",
25647
- "",
25648
- "## Default Paths",
25649
- "",
25650
- `If the project has no override in \`${paths.docsRoot}/project-context.md\` or`,
25651
- "`agentConfig.rules`, outputs land under:",
25652
- "",
25653
- `- \`${paths.requirementsRoot}/<category-dir>/<PREFIX>-<NNN>-<slug>.md\``,
25654
- `- \`${paths.requirementsRoot}/<category-dir>/README.md\` (registry update)`,
25655
- `- \`${paths.requirementsRoot}/README.md\` (top-level README, generated on`,
25656
- " first use only)",
26117
+ "/scan-requirements <scope>",
25657
26118
  "",
25658
- "Templates and the standards reference ship with this skill under",
25659
- "`_references/templates/` and `_references/standards-and-frameworks.md`.",
26119
+ "Where `<scope>` is one of:",
26120
+ "- `bcm:<PREFIX-NNN>` \u2014 a single BCM model doc",
26121
+ "- `competitive:<slug>` \u2014 a single competitive analysis doc",
26122
+ "- `product-roadmap` \u2014 the prioritized feature roadmap",
26123
+ "- `entity-taxonomy` \u2014 the entity taxonomy doc",
26124
+ "- `meeting:<slug>` \u2014 a meeting extract",
26125
+ "- `all-bcm` / `all-competitive` \u2014 full sweep (long-running)",
25660
26126
  "",
25661
26127
  "## Steps",
25662
26128
  "",
25663
- "1. Create a `req:write` issue with `type:requirement`,",
25664
- ` \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\`, \`status:${labelsForPhase(issueDefaults, "req:write").status}\`, and the matching \`tier:*\` label.`,
25665
- " Body must include the category, tier, output path, and a pointer",
25666
- " to the upstream proposal (or a direct user description if no",
25667
- " proposals file exists).",
25668
- "2. Execute the write phase of the requirements-writer agent.",
25669
- "3. The agent writes one requirement document, updates the category",
25670
- " index, opens a PR, and closes the issue.",
26129
+ `1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
26130
+ ` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
26131
+ "2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
26132
+ "3. If gaps are found, a `req:draft-trace` issue is created automatically.",
25671
26133
  "",
25672
26134
  "## Output",
25673
26135
  "",
25674
- "- One requirement document under `<REQUIREMENTS_ROOT>` following the",
25675
- " shipped category template, with `Status: Draft` for direct-write",
25676
- " categories or `Status: Proposed` for ADR/TR",
25677
- "- A category-index row pointing at the new document",
25678
- "- (First-time only) a top-level requirements README derived from",
25679
- " `_template-requirements-README.md`"
26136
+ "- A `req-scan-<scope>-<YYYY-MM-DD>.md` file under the project's research",
26137
+ " requirements directory.",
26138
+ "- A `req:draft-trace` issue if any gaps were identified."
25680
26139
  ].join("\n")
25681
26140
  };
25682
26141
  }
25683
- function buildRequirementsWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
26142
+ function buildRequirementsAnalystBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
25684
26143
  return {
25685
- name: "requirements-writer",
25686
- description: "Requirements writer agent bundle. Authors formal requirement documents from upstream proposals using the 11-category taxonomy (BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT), the four-tier classification, and decision-authority rules (direct-write vs. propose-only). Ships 13 templates plus a standards-and-frameworks reference.",
26144
+ name: "requirements-analyst",
26145
+ description: "Requirements gap-discovery agent bundle for BCM-driven projects. 2-phase pipeline (scan, draft-trace) with req:* phase labels.",
25687
26146
  appliesWhen: () => true,
25688
26147
  rules: [
25689
26148
  {
25690
- name: "requirements-writer-workflow",
25691
- description: "Describes the requirements-writer pipeline, the req:write phase label, the four tier:* labels, and the boundary with the upstream requirements-analyst and bcm-writer bundles.",
26149
+ name: "requirements-analyst-workflow",
26150
+ description: "Describes the 2-phase requirements gap-discovery pipeline, the req:* label taxonomy, and the boundary with the downstream requirements-writer agent.",
25692
26151
  scope: AGENT_RULE_SCOPE.ALWAYS,
25693
26152
  content: [
25694
- "# Requirements Writer Workflow",
25695
- "",
25696
- "Use `/write-requirement <category> <short-title>` to author one",
25697
- "formal requirement document. The writer runs in a single phase",
25698
- "tracked by a GitHub issue labeled `req:write` plus the matching",
25699
- "`tier:*` label. Issues also carry `type:requirement` (declared",
25700
- "by the upstream `requirements-analyst` bundle).",
25701
- "",
25702
- "The pipeline produces **requirement documents only** \u2014 capability",
25703
- "models are written by the `bcm-writer` agent and gap discovery is",
25704
- "the responsibility of the `requirements-analyst` agent. The",
25705
- "writer never opens `req:scan`, `req:draft-trace`, or `bcm:*`",
25706
- "issues.",
26153
+ "# Requirements Analyst Workflow",
25707
26154
  "",
25708
- "Documents follow the 11-category taxonomy (BR, FR, NFR, TR, ADR,",
25709
- "SEC, DR, INT, OPS, UX, MT) and the four-tier classification",
25710
- "(Platform, Industry, Customer Workflow, Consumer Application).",
25711
- "Templates and a standards-and-frameworks reference ship with the",
25712
- "skill \u2014 they are the same files for every project that adopts",
25713
- "the bundle.",
26155
+ "Use `/scan-requirements <scope>` to kick off a requirements gap",
26156
+ "discovery cycle. The pipeline runs in 2 phases \u2014 scan and",
26157
+ "draft-trace \u2014 each tracked by its own GitHub issue labeled",
26158
+ "`req:scan` or `req:draft-trace`. All issues carry",
26159
+ "`type:requirement`.",
25714
26160
  "",
25715
- "Decision-authority rules are non-negotiable: BR / FR / NFR /",
25716
- "SEC / UX ship as `Status: Draft`; ADR and TR ship as",
25717
- "`Status: Proposed` with a Recommendation framed for human",
25718
- "decision; DR / MT / INT / OPS spin technology choices off into",
25719
- "separate `Proposed` ADR or TR documents.",
26161
+ "The requirements-analyst *discovers gaps, drafts proposals, and",
26162
+ "opens `req:write` issues for the downstream writer*; it does",
26163
+ "**not** write final requirement documents. Writing is the job of",
26164
+ "the downstream `requirements-writer` agent (a separate bundle).",
26165
+ "Keep that boundary clean: proposals land under the research",
26166
+ "requirements directory, not under the authoritative requirements",
26167
+ "tree. The draft-trace phase tags new issues with `req:write` so",
26168
+ "the writer bundle picks them up automatically.",
25720
26169
  "",
25721
- "See the `requirements-writer` agent definition for full workflow",
25722
- "details, configurable paths, decision-authority rules, and",
25723
- "phase-by-phase instructions."
26170
+ "See the `requirements-analyst` agent definition for full workflow",
26171
+ "details and phase-by-phase instructions."
25724
26172
  ].join("\n"),
25725
26173
  platforms: {
25726
26174
  cursor: { exclude: true }
@@ -25728,38 +26176,34 @@ function buildRequirementsWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefault
25728
26176
  tags: ["workflow"]
25729
26177
  }
25730
26178
  ],
25731
- skills: [buildWriteRequirementSkill(paths, issueDefaults)],
25732
- subAgents: [buildRequirementsWriterSubAgent(paths)],
26179
+ skills: [buildScanRequirementsSkill(issueDefaults)],
26180
+ subAgents: [buildRequirementsAnalystSubAgent(paths, issueDefaults)],
26181
+ // Shares the write-start ID allocator with the writer bundle. Both
26182
+ // bundles auto-detect and AgentConfig.resolveProcedures dedupes by
26183
+ // name, so `next-requirement-id.sh` is emitted to disk exactly once.
26184
+ // The analyst runs it (without a self-issue-number) to seed the first
26185
+ // reserved ID in a category during draft-trace.
26186
+ procedures: [nextRequirementIdProcedure],
25733
26187
  labels: [
25734
26188
  {
25735
- name: "req:write",
25736
- color: "FEF2C0",
25737
- description: "Phase: write a formal requirement document using the requirements-writer skill"
25738
- },
25739
- {
25740
- name: "tier:platform",
25741
- color: "EDEDED",
25742
- description: "Architectural tier: core platform (shared infrastructure, APIs, auth, tenant isolation)"
25743
- },
25744
- {
25745
- name: "tier:industry",
25746
- color: "EDEDED",
25747
- description: "Architectural tier: industry vertical (capabilities not every tenant needs)"
26189
+ name: "type:requirement",
26190
+ color: "1D76DB",
26191
+ description: "Work that produces or discovers a requirement document (FR, BR, NFR, etc.)"
25748
26192
  },
25749
26193
  {
25750
- name: "tier:customer-workflow",
25751
- color: "EDEDED",
25752
- description: "Architectural tier: customer-configured workflow (business logic tenants configure)"
26194
+ name: "req:scan",
26195
+ color: "C5DEF5",
26196
+ description: "Phase 1: scan source docs for requirement gaps and deduplicate"
25753
26197
  },
25754
26198
  {
25755
- name: "tier:consumer-app",
25756
- color: "EDEDED",
25757
- description: "Architectural tier: consumer application (UI/UX and integrations in external front-ends/systems)"
26199
+ name: "req:draft-trace",
26200
+ color: "BFDADC",
26201
+ description: "Phase 2: draft proposals, create req:write issues, and backfill source-doc traceability"
25758
26202
  }
25759
26203
  ]
25760
26204
  };
25761
26205
  }
25762
- var requirementsWriterBundle = buildRequirementsWriterBundle();
26206
+ var requirementsAnalystBundle = buildRequirementsAnalystBundle();
25763
26207
 
25764
26208
  // src/agent/bundles/requirements-reviewer.ts
25765
26209
  function buildRequirementsReviewerSubAgent(paths, issueDefaults) {
@@ -30475,7 +30919,7 @@ var _TurboRepo = class _TurboRepo extends Component4 {
30475
30919
  }
30476
30920
  }
30477
30921
  activateBranchNameEnvVar(options) {
30478
- const value = '$(echo "${GIT_BRANCH_NAME:-$(git rev-parse --abbrev-ref HEAD)}")';
30922
+ const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
30479
30923
  if (options === void 0) {
30480
30924
  this.project.logger.warn(
30481
30925
  "TurboRepo.activateBranchNameEnvVar() with no arguments is deprecated. It writes GIT_BRANCH_NAME to the root `globalEnv`, which forces every task in the monorepo to miss cache on every branch switch. Pass `{ tasks: [...] }` and name only the tasks that actually consume the branch (e.g. CDK synth/package) to preserve cross-branch cache hits for everything else."
@@ -30988,7 +31432,7 @@ var VERSION = {
30988
31432
  /**
30989
31433
  * Version of `@types/node` to use across all packages (pnpm catalog).
30990
31434
  */
30991
- TYPES_NODE_VERSION: "26.1.0",
31435
+ TYPES_NODE_VERSION: "26.1.1",
30992
31436
  /**
30993
31437
  * What version of Vite to use (pnpm override). Pinned to 5.x so Vitest 4.x
30994
31438
  * can load config (Vite 6+/7+ are ESM-only; see issue #142). Remove override
@@ -32680,7 +33124,11 @@ var DEFAULT_CLAUDE_HOOKS = {
32680
33124
  hooks: [
32681
33125
  {
32682
33126
  type: "command",
32683
- command: `echo "$(date -u +%FT%TZ),\${CLAUDE_SESSION_ID:-unknown},\${CLAUDE_INPUT_TOKENS:-0},\${CLAUDE_OUTPUT_TOKENS:-0}" >> ${DEFAULT_USAGE_LOG_PATH}`
33127
+ // Claude Code delivers hook data as a JSON object on stdin, not
33128
+ // as CLAUDE_* env vars, so read the session id from `.session_id`
33129
+ // via jq. Token counts are not passed to hooks, so the two token
33130
+ // columns are dropped — usage.log is now `<timestamp>,<session-id>`.
33131
+ command: `sid="$(jq -r '.session_id // "unknown"' 2>/dev/null)"; echo "$(date -u +%FT%TZ),\${sid:-unknown}" >> ${DEFAULT_USAGE_LOG_PATH}`
32684
33132
  }
32685
33133
  ]
32686
33134
  }
@@ -32691,7 +33139,13 @@ var DEFAULT_CLAUDE_HOOKS = {
32691
33139
  hooks: [
32692
33140
  {
32693
33141
  type: "command",
32694
- command: 'case "${CLAUDE_TOOL_INPUT_path:-}" in *docs/src/content/docs/*) if [ -x .claude/procedures/strip-tool-artifact-tags.sh ]; then bash .claude/procedures/strip-tool-artifact-tags.sh "$CLAUDE_TOOL_INPUT_path" >/dev/null 2>&1; fi; if [ -x .claude/procedures/check-links.sh ]; then bash .claude/procedures/check-links.sh "$CLAUDE_TOOL_INPUT_path" 2>&1 | head -20; fi ;; esac'
33142
+ // Claude Code delivers hook data as a JSON object on stdin, not
33143
+ // as CLAUDE_* env vars, so read the edited file path from
33144
+ // `.tool_input.file_path` via jq. On files under
33145
+ // docs/src/content/docs/ this runs only the tool-artifact-tag
33146
+ // stripper (#779); it is `-x`-guarded so it no-ops cleanly when
33147
+ // the path is empty or the procedure script is not shipped.
33148
+ command: `path="$(jq -r '.tool_input.file_path // empty' 2>/dev/null)"; case "$path" in *docs/src/content/docs/*) if [ -x .claude/procedures/strip-tool-artifact-tags.sh ]; then bash .claude/procedures/strip-tool-artifact-tags.sh "$path" >/dev/null 2>&1; fi ;; esac`
32695
33149
  }
32696
33150
  ]
32697
33151
  }
@@ -32779,7 +33233,12 @@ var AgentConfig = class _AgentConfig extends Component8 {
32779
33233
  * entries. Both `allow` and `deny` are deduped via
32780
33234
  * `Array.from(new Set(...))`; V8 `Set` iteration preserves insertion
32781
33235
  * order, so the final ordering is defaults first, then bundle, then
32782
- * user, with duplicates removed (first-occurrence wins). `defaultMode`
33236
+ * user, with duplicates removed (first-occurrence wins). After the
33237
+ * merge+dedupe, any entry whose value exactly matches a string in
33238
+ * `permissions.excludeDefaults` is dropped from `allow` / `deny` / `ask`
33239
+ * — the only supported way to remove a baseline default, since the merge
33240
+ * is otherwise append-only. `excludeDefaults` is a build-time directive
33241
+ * and is stripped from the rendered `permissions` object. `defaultMode`
32783
33242
  * is opt-in (steering decision D7): it is set only from
32784
33243
  * `userSettings.defaultMode` and left undefined otherwise, so the
32785
33244
  * renderer omits the key for un-opted-in consumers — see the inline
@@ -32804,6 +33263,29 @@ var AgentConfig = class _AgentConfig extends Component8 {
32804
33263
  const bundleDeny = bundlePermissions?.deny ?? [];
32805
33264
  const userAllow = userSettings?.permissions?.allow ?? [];
32806
33265
  const userDeny = userSettings?.permissions?.deny ?? [];
33266
+ const exclude = new Set(userSettings?.permissions?.excludeDefaults ?? []);
33267
+ const dropExcluded = (entries) => entries.filter((entry) => !exclude.has(entry));
33268
+ const { excludeDefaults: _excludeDefaults, ...passthroughPermissions } = userSettings?.permissions ?? {};
33269
+ const askOverride = passthroughPermissions.ask?.length ? { ask: dropExcluded([...passthroughPermissions.ask]) } : {};
33270
+ const mergedPermissions = {
33271
+ ...passthroughPermissions,
33272
+ allow: dropExcluded(
33273
+ Array.from(
33274
+ /* @__PURE__ */ new Set([...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow])
33275
+ )
33276
+ ),
33277
+ deny: dropExcluded(
33278
+ Array.from(
33279
+ /* @__PURE__ */ new Set([
33280
+ ...DEFAULT_CLAUDE_DENY,
33281
+ ...DEFAULT_CLAUDE_PATH_DENY,
33282
+ ...bundleDeny,
33283
+ ...userDeny
33284
+ ])
33285
+ )
33286
+ ),
33287
+ ...askOverride
33288
+ };
32807
33289
  return {
32808
33290
  ...userSettings,
32809
33291
  // `defaultMode` is opt-in (steering decision D7): the base
@@ -32816,20 +33298,7 @@ var AgentConfig = class _AgentConfig extends Component8 {
32816
33298
  // confirmation prompts — opt in by setting
32817
33299
  // `claudeSettings.defaultMode: "dontAsk"`.
32818
33300
  defaultMode: userSettings?.defaultMode,
32819
- permissions: {
32820
- ...userSettings?.permissions,
32821
- allow: Array.from(
32822
- /* @__PURE__ */ new Set([...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow])
32823
- ),
32824
- deny: Array.from(
32825
- /* @__PURE__ */ new Set([
32826
- ...DEFAULT_CLAUDE_DENY,
32827
- ...DEFAULT_CLAUDE_PATH_DENY,
32828
- ...bundleDeny,
32829
- ...userDeny
32830
- ])
32831
- )
32832
- },
33301
+ permissions: mergedPermissions,
32833
33302
  hooks: _AgentConfig.mergeClaudeHooks(userSettings),
32834
33303
  env: { ...DEFAULT_CLAUDE_ENV, ...userSettings?.env ?? {} }
32835
33304
  };
@@ -34673,7 +35142,7 @@ var AwsDeploymentConfig = class _AwsDeploymentConfig extends Component12 {
34673
35142
  };
34674
35143
  this.cdkCli = CdkCli.of(project) || new CdkCli(project);
34675
35144
  this.env = {
34676
- GIT_BRANCH_NAME: '$(echo "${GIT_BRANCH_NAME:-$(git branch --show-current)}")'
35145
+ GIT_BRANCH_NAME: '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git branch --show-current)'
34677
35146
  };
34678
35147
  this.projectPath = relative3(project.root.outdir, project.outdir);
34679
35148
  this.rootPath = relative3(project.outdir, project.root.outdir);
@@ -37252,8 +37721,62 @@ function patchStepArray3(steps, pinned) {
37252
37721
  }
37253
37722
  }
37254
37723
 
37724
+ // src/workflows/requirement-issue-template.ts
37725
+ import { Component as Component18, TextFile as TextFile7 } from "projen";
37726
+ function renderRequirementBlock(fields) {
37727
+ return [
37728
+ "## Requirement",
37729
+ "",
37730
+ `- **ID:** ${fields.id}`,
37731
+ `- **Spec:** ${fields.specUrl}`,
37732
+ `- **Tier:** ${fields.tier}`,
37733
+ `- **Customer:** ${fields.customer}`
37734
+ ].join("\n");
37735
+ }
37736
+ var DEFAULT_TEMPLATE_PATH = ".github/ISSUE_TEMPLATE/requirement-impl.md";
37737
+ var DEFAULT_TEMPLATE_LABELS = ["type:feat"];
37738
+ var RequirementIssueTemplate = class extends Component18 {
37739
+ constructor(project, options = {}) {
37740
+ super(project);
37741
+ const path8 = options.path ?? DEFAULT_TEMPLATE_PATH;
37742
+ const labels = options.labels ?? DEFAULT_TEMPLATE_LABELS;
37743
+ const labelsYaml = `[${labels.map((l) => `"${l}"`).join(", ")}]`;
37744
+ const requirementBlock = renderRequirementBlock({
37745
+ id: "<!-- e.g. FR-042 -->",
37746
+ specUrl: "<!-- URL to the canonical requirement doc in the planning repo -->",
37747
+ tier: "<!-- Platform | Industry | Customer Workflow | Consumer Application -->",
37748
+ customer: "<!-- customer scope, or \u2014 -->"
37749
+ });
37750
+ const lines = [
37751
+ "---",
37752
+ "name: Requirement implementation",
37753
+ "about: Implement a canonical requirement tracked in the planning repo",
37754
+ 'title: "feat: implement <REQ-ID> \u2014 <short summary>"',
37755
+ `labels: ${labelsYaml}`,
37756
+ "---",
37757
+ "",
37758
+ ...requirementBlock.split("\n"),
37759
+ "",
37760
+ "> Add the `req:<ID>` label (e.g. `req:FR-042`) so this issue is traceable to the requirement across repos.",
37761
+ "",
37762
+ "## Summary",
37763
+ "",
37764
+ "<!-- What must change in THIS repo to satisfy the requirement above. -->",
37765
+ "",
37766
+ "## Acceptance Criteria",
37767
+ "",
37768
+ "- [ ]"
37769
+ ];
37770
+ new TextFile7(project, path8, {
37771
+ marker: false,
37772
+ committed: true,
37773
+ lines
37774
+ });
37775
+ }
37776
+ };
37777
+
37255
37778
  // src/workflows/sync-labels.ts
37256
- import { Component as Component18, YamlFile as YamlFile2 } from "projen";
37779
+ import { Component as Component19, YamlFile as YamlFile2 } from "projen";
37257
37780
  import { JobPermission as JobPermission4 } from "projen/lib/github/workflows-model";
37258
37781
  var DEFAULT_STATUS_LABELS = [
37259
37782
  {
@@ -37461,7 +37984,7 @@ ${offenders}`
37461
37984
  }
37462
37985
  });
37463
37986
  }
37464
- var LabelsFile = class extends Component18 {
37987
+ var LabelsFile = class extends Component19 {
37465
37988
  constructor(project, labels) {
37466
37989
  super(project);
37467
37990
  new YamlFile2(project, LABELS_CONFIG_PATH, {
@@ -37678,6 +38201,9 @@ var MonorepoProject = class extends TypeScriptAppProject {
37678
38201
  super({ ...options });
37679
38202
  postInstallDependenciesMap.set(this, []);
37680
38203
  this.tsconfig?.removeInclude(`${this.srcdir}/**/*.ts`);
38204
+ this.preCompileTask.exec(
38205
+ '[ -e "tsconfig.tsbuildinfo" ] && rm -f tsconfig.tsbuildinfo || true'
38206
+ );
37681
38207
  this.pnpmVersion = options.pnpmVersion;
37682
38208
  this.configulatorRegistryConsumer = options.configulatorRegistryConsumer ?? true;
37683
38209
  this.layoutEnforcement = options.layoutEnforcement ?? LAYOUT_ENFORCEMENT.WARN;
@@ -37708,7 +38234,16 @@ var MonorepoProject = class extends TypeScriptAppProject {
37708
38234
  }
37709
38235
  if (options.resetTask !== false) {
37710
38236
  const defaultResetTaskOptions = {
37711
- pathsToRemove: ["node_modules", ".turbo", "dist", "lib"]
38237
+ pathsToRemove: [
38238
+ "node_modules",
38239
+ ".turbo",
38240
+ "dist",
38241
+ "lib",
38242
+ // Root reset must also drop the incremental buildinfo (#656) so a
38243
+ // stale `.tsbuildinfo` can't hide a regressed dependency `.d.ts`
38244
+ // rollup from the next compile. Sub-projects already clean this.
38245
+ "tsconfig.tsbuildinfo"
38246
+ ]
37712
38247
  };
37713
38248
  const userResetTaskOptions = options.resetTaskOptions ?? {};
37714
38249
  const resetTaskOptions = merge(
@@ -37752,6 +38287,12 @@ var MonorepoProject = class extends TypeScriptAppProject {
37752
38287
  bundles: syncLabelsOptions.bundles ?? agentConfig?.activeBundles
37753
38288
  });
37754
38289
  }
38290
+ if (options.requirementIssueTemplate) {
38291
+ new RequirementIssueTemplate(
38292
+ this,
38293
+ typeof options.requirementIssueTemplate === "object" ? options.requirementIssueTemplate : {}
38294
+ );
38295
+ }
37755
38296
  if (this.buildWorkflow) {
37756
38297
  addBuildCompleteJob(this.buildWorkflow);
37757
38298
  }
@@ -37887,11 +38428,11 @@ var MonorepoProject = class extends TypeScriptAppProject {
37887
38428
  };
37888
38429
 
37889
38430
  // src/typescript/tsdoc-config.ts
37890
- import { Component as Component19, JsonFile as JsonFile6 } from "projen";
38431
+ import { Component as Component20, JsonFile as JsonFile6 } from "projen";
37891
38432
  var STANDARD_MODIFIER_TAGS = ["@default"];
37892
38433
  var STANDARD_INLINE_TAGS = ["@code"];
37893
38434
  var ALWAYS_ON_SCOPES = ["@codedrifters"];
37894
- var TsdocConfig = class _TsdocConfig extends Component19 {
38435
+ var TsdocConfig = class _TsdocConfig extends Component20 {
37895
38436
  /**
37896
38437
  * Derive a workspace scope from a scoped package name (`@scope/name`).
37897
38438
  * Returns `undefined` when the name is unscoped.
@@ -37949,9 +38490,9 @@ var TsdocConfig = class _TsdocConfig extends Component19 {
37949
38490
 
37950
38491
  // src/typescript/typescript-config.ts
37951
38492
  import { relative as relative7 } from "path";
37952
- import { Component as Component20 } from "projen";
38493
+ import { Component as Component21 } from "projen";
37953
38494
  import { ensureRelativePathStartsWithDot } from "projen/lib/util/path";
37954
- var TypeScriptConfig = class extends Component20 {
38495
+ var TypeScriptConfig = class extends Component21 {
37955
38496
  constructor(project) {
37956
38497
  super(project);
37957
38498
  let tsPaths = {};
@@ -38351,12 +38892,12 @@ import { merge as merge4 } from "ts-deepmerge";
38351
38892
 
38352
38893
  // src/workflows/aws-deploy-workflow.ts
38353
38894
  var import_utils11 = __toESM(require_lib());
38354
- import { Component as Component21 } from "projen";
38895
+ import { Component as Component22 } from "projen";
38355
38896
  import { BuildWorkflow } from "projen/lib/build";
38356
38897
  import { GitHub as GitHub5, WorkflowSteps as WorkflowSteps2 } from "projen/lib/github";
38357
38898
  import { JobPermission as JobPermission5 } from "projen/lib/github/workflows-model";
38358
38899
  var PROD_DEPLOY_NAME = "prod-deploy";
38359
- var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component21 {
38900
+ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component22 {
38360
38901
  constructor(project, options = {}) {
38361
38902
  super(project);
38362
38903
  this.project = project;
@@ -38673,7 +39214,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component21 {
38673
39214
  };
38674
39215
 
38675
39216
  // src/workflows/aws-teardown-workflow.ts
38676
- import { Component as Component22 } from "projen";
39217
+ import { Component as Component23 } from "projen";
38677
39218
  import { GitHub as GitHub6, GithubWorkflow } from "projen/lib/github";
38678
39219
  import { JobPermission as JobPermission6 } from "projen/lib/github/workflows-model";
38679
39220
  var DEFAULT_TEARDOWN_BRANCH_PATTERNS = [
@@ -38695,7 +39236,7 @@ var resolveBranchPatterns = (explicit, targets) => {
38695
39236
  }
38696
39237
  return [...DEFAULT_TEARDOWN_BRANCH_PATTERNS];
38697
39238
  };
38698
- var AwsTeardownWorkflow = class extends Component22 {
39239
+ var AwsTeardownWorkflow = class extends Component23 {
38699
39240
  constructor(rootProject, options) {
38700
39241
  super(rootProject);
38701
39242
  this.rootProject = rootProject;
@@ -39087,7 +39628,7 @@ var AwsCdkProject = class extends awscdk.AwsCdkTypeScriptApp {
39087
39628
  };
39088
39629
 
39089
39630
  // src/projects/react-vite-site-project.ts
39090
- import { SampleFile as SampleFile5, TextFile as TextFile7 } from "projen";
39631
+ import { SampleFile as SampleFile5, TextFile as TextFile8 } from "projen";
39091
39632
  import { merge as merge5 } from "ts-deepmerge";
39092
39633
  var ReactViteSiteProject = class extends TypeScriptProject {
39093
39634
  constructor(userOptions) {
@@ -39126,7 +39667,7 @@ var ReactViteSiteProject = class extends TypeScriptProject {
39126
39667
  };
39127
39668
  super(options);
39128
39669
  this.package.addField("type", "module");
39129
- new TextFile7(this, ".nvmrc", { lines: ["v24.11.0"] });
39670
+ new TextFile8(this, ".nvmrc", { lines: ["v24.11.0"] });
39130
39671
  this.tsconfig?.file.addOverride("compilerOptions.target", "ES2020");
39131
39672
  this.tsconfig?.file.addOverride("compilerOptions.lib", [
39132
39673
  "ES2020",
@@ -39627,6 +40168,7 @@ export {
39627
40168
  ROOT_CI_TASK_NAME,
39628
40169
  ROOT_TURBO_TASK_NAME,
39629
40170
  ReactViteSiteProject,
40171
+ RequirementIssueTemplate,
39630
40172
  ResetTask,
39631
40173
  SCHEDULED_TASK_MODEL_VALUES,
39632
40174
  SCOPE_CLASS_VALUES,
@@ -39727,6 +40269,7 @@ export {
39727
40269
  maintenanceAuditBundle,
39728
40270
  meetingAnalysisBundle,
39729
40271
  mergeCdkOptions,
40272
+ nextRequirementIdProcedure,
39730
40273
  orchestratorBundle,
39731
40274
  parseApiRollup,
39732
40275
  peopleProfileBundle,
@@ -39774,11 +40317,13 @@ export {
39774
40317
  renderIssueTemplatesRuleContent,
39775
40318
  renderIssueTemplatesStarterPage,
39776
40319
  renderMeetingTypesSection,
40320
+ renderNextRequirementIdProcedure,
39777
40321
  renderPriorityRulesSection,
39778
40322
  renderProgressFileName,
39779
40323
  renderProgressFilePath,
39780
40324
  renderProgressFilesBundleHook,
39781
40325
  renderProgressFilesRuleContent,
40326
+ renderRequirementBlock,
39782
40327
  renderRunRatioSection,
39783
40328
  renderRunRatioShellHelpers,
39784
40329
  renderScheduledTaskSkillFile,