@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.d.mts +161 -3
- package/lib/index.d.ts +162 -4
- package/lib/index.js +1445 -896
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +1858 -1313
- package/lib/index.mjs.map +1 -1
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -291,6 +291,7 @@ __export(index_exports, {
|
|
|
291
291
|
ROOT_CI_TASK_NAME: () => ROOT_CI_TASK_NAME,
|
|
292
292
|
ROOT_TURBO_TASK_NAME: () => ROOT_TURBO_TASK_NAME,
|
|
293
293
|
ReactViteSiteProject: () => ReactViteSiteProject,
|
|
294
|
+
RequirementIssueTemplate: () => RequirementIssueTemplate,
|
|
294
295
|
ResetTask: () => ResetTask,
|
|
295
296
|
SCHEDULED_TASK_MODEL_VALUES: () => SCHEDULED_TASK_MODEL_VALUES,
|
|
296
297
|
SCOPE_CLASS_VALUES: () => SCOPE_CLASS_VALUES,
|
|
@@ -391,6 +392,7 @@ __export(index_exports, {
|
|
|
391
392
|
maintenanceAuditBundle: () => maintenanceAuditBundle,
|
|
392
393
|
meetingAnalysisBundle: () => meetingAnalysisBundle,
|
|
393
394
|
mergeCdkOptions: () => mergeCdkOptions,
|
|
395
|
+
nextRequirementIdProcedure: () => nextRequirementIdProcedure,
|
|
394
396
|
orchestratorBundle: () => orchestratorBundle,
|
|
395
397
|
parseApiRollup: () => parseApiRollup,
|
|
396
398
|
peopleProfileBundle: () => peopleProfileBundle,
|
|
@@ -438,11 +440,13 @@ __export(index_exports, {
|
|
|
438
440
|
renderIssueTemplatesRuleContent: () => renderIssueTemplatesRuleContent,
|
|
439
441
|
renderIssueTemplatesStarterPage: () => renderIssueTemplatesStarterPage,
|
|
440
442
|
renderMeetingTypesSection: () => renderMeetingTypesSection,
|
|
443
|
+
renderNextRequirementIdProcedure: () => renderNextRequirementIdProcedure,
|
|
441
444
|
renderPriorityRulesSection: () => renderPriorityRulesSection,
|
|
442
445
|
renderProgressFileName: () => renderProgressFileName,
|
|
443
446
|
renderProgressFilePath: () => renderProgressFilePath,
|
|
444
447
|
renderProgressFilesBundleHook: () => renderProgressFilesBundleHook,
|
|
445
448
|
renderProgressFilesRuleContent: () => renderProgressFilesRuleContent,
|
|
449
|
+
renderRequirementBlock: () => renderRequirementBlock,
|
|
446
450
|
renderRunRatioSection: () => renderRunRatioSection,
|
|
447
451
|
renderRunRatioShellHelpers: () => renderRunRatioShellHelpers,
|
|
448
452
|
renderScheduledTaskSkillFile: () => renderScheduledTaskSkillFile,
|
|
@@ -14235,6 +14239,16 @@ var DEFAULT_BUNDLE_OVERRIDES = {
|
|
|
14235
14239
|
"meeting:link": {
|
|
14236
14240
|
acceptanceCriteria: { smallMax: 3, mediumMax: 15 },
|
|
14237
14241
|
sources: { smallMax: 2, mediumMax: 10 }
|
|
14242
|
+
},
|
|
14243
|
+
"type:docs": {
|
|
14244
|
+
acceptanceCriteria: { smallMax: 3, mediumMax: 12 }
|
|
14245
|
+
},
|
|
14246
|
+
"bcm:context": {
|
|
14247
|
+
acceptanceCriteria: { smallMax: 3, mediumMax: 12 }
|
|
14248
|
+
},
|
|
14249
|
+
"req:scan": {
|
|
14250
|
+
acceptanceCriteria: { smallMax: 3, mediumMax: 12 },
|
|
14251
|
+
sources: { smallMax: 2, mediumMax: 12 }
|
|
14238
14252
|
}
|
|
14239
14253
|
};
|
|
14240
14254
|
var DEFAULT_DECOMPOSITION_TEMPLATE = [
|
|
@@ -15283,6 +15297,55 @@ function shellSingleQuote(value) {
|
|
|
15283
15297
|
}
|
|
15284
15298
|
|
|
15285
15299
|
// src/agent/bundles/orchestrator.ts
|
|
15300
|
+
function renderDelegationActiveSignalsHelper() {
|
|
15301
|
+
return [
|
|
15302
|
+
"# delegation_active_signals <pr-number> <head-oid>",
|
|
15303
|
+
"# Echo a \\x1f-separated triple: <fixlist_at><US><report_at><US><head_at>",
|
|
15304
|
+
"# (US = ASCII Unit Separator; see the printf below for why not a tab).",
|
|
15305
|
+
"# Each field is an ISO-8601 timestamp or empty. Shared by the",
|
|
15306
|
+
"# feedback-drain (cmd_needs_worker) and the lease-reconcile",
|
|
15307
|
+
"# (cmd_lease_reconcile) so both judge a delegation 'active' the same",
|
|
15308
|
+
"# way. See the block comment above the renderer for field semantics.",
|
|
15309
|
+
"delegation_active_signals() {",
|
|
15310
|
+
' local pr_num="$1"',
|
|
15311
|
+
' local head_oid="${2:-}"',
|
|
15312
|
+
"",
|
|
15313
|
+
" # Pull the PR comments once; derive both the fix-list reference time",
|
|
15314
|
+
" # and the newest worker-report time from the same payload.",
|
|
15315
|
+
" local comments",
|
|
15316
|
+
' comments=$(gh pr view "$pr_num" --json comments \\',
|
|
15317
|
+
` --jq '.comments' 2>/dev/null || echo "[]")`,
|
|
15318
|
+
"",
|
|
15319
|
+
" # Newest fix-list comment createdAt (empty if none found).",
|
|
15320
|
+
" local fixlist_at",
|
|
15321
|
+
` fixlist_at=$(echo "$comments" | jq -r '`,
|
|
15322
|
+
' [.[] | select(.body | contains("## Reviewer: fix list for @issue-worker"))]',
|
|
15323
|
+
` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
|
|
15324
|
+
"",
|
|
15325
|
+
" # Newest worker-report createdAt (empty if none found). Keyed off the",
|
|
15326
|
+
" # stable HTML marker complete-feedback-handoff.sh embeds.",
|
|
15327
|
+
" local report_at",
|
|
15328
|
+
` report_at=$(echo "$comments" | jq -r '`,
|
|
15329
|
+
' [.[] | select(.body | contains("issue-worker feedback hand-off \u2014 review:needs-worker cleared"))]',
|
|
15330
|
+
` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
|
|
15331
|
+
"",
|
|
15332
|
+
" # Branch HEAD committer date (empty when no head-oid or lookup fails).",
|
|
15333
|
+
' local head_at=""',
|
|
15334
|
+
' if [[ -n "$head_oid" ]]; then',
|
|
15335
|
+
' head_at=$(gh api "repos/{owner}/{repo}/commits/${head_oid}" \\',
|
|
15336
|
+
` --jq '.commit.committer.date // ""' 2>/dev/null || echo "")`,
|
|
15337
|
+
" fi",
|
|
15338
|
+
"",
|
|
15339
|
+
" # Emit the triple delimited by the ASCII Unit Separator (\\x1f), NOT a",
|
|
15340
|
+
" # tab: any field may be empty, and `read` with a whitespace IFS (tab",
|
|
15341
|
+
" # counts) collapses adjacent empty fields \u2014 which would shift head_at",
|
|
15342
|
+
" # into report_at. \\x1f is non-whitespace and never appears in a",
|
|
15343
|
+
" # timestamp, so callers `IFS=$'\\x1f' read -r fixlist_at report_at",
|
|
15344
|
+
" # head_at` recover all three fields even when the middle one is empty.",
|
|
15345
|
+
` printf '%s\\x1f%s\\x1f%s\\n' "$fixlist_at" "$report_at" "$head_at"`,
|
|
15346
|
+
"}"
|
|
15347
|
+
];
|
|
15348
|
+
}
|
|
15286
15349
|
function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
15287
15350
|
const tierCase = renderAgentTierCaseStatement(tiers);
|
|
15288
15351
|
const scopeHelper = renderScopeGateShellHelpers(scopeGate);
|
|
@@ -15345,12 +15408,20 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15345
15408
|
"",
|
|
15346
15409
|
scopeHelperIndented,
|
|
15347
15410
|
"",
|
|
15411
|
+
...renderDelegationActiveSignalsHelper(),
|
|
15412
|
+
"",
|
|
15348
15413
|
"# \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",
|
|
15349
15414
|
"",
|
|
15350
15415
|
"cmd_unblock() {",
|
|
15416
|
+
" # Fetch ALL open status:blocked issues, not just the first page (#823).",
|
|
15417
|
+
" # This is a label-filtered `gh issue list` (REST issues endpoint, which gh",
|
|
15418
|
+
" # paginates up to --limit) \u2014 NOT a --search query \u2014 so a large ceiling is",
|
|
15419
|
+
" # safe from the search-API secondary rate limits that cap unblock-dependents",
|
|
15420
|
+
" # at 100. An all-deps-closed issue must be reachable regardless of backlog",
|
|
15421
|
+
" # position.",
|
|
15351
15422
|
" local issues",
|
|
15352
15423
|
' issues=$(gh issue list --label "status:blocked" --state open \\',
|
|
15353
|
-
' --json number,body --limit
|
|
15424
|
+
' --json number,body --limit 1000 2>/dev/null || echo "[]")',
|
|
15354
15425
|
"",
|
|
15355
15426
|
" local count",
|
|
15356
15427
|
` count=$(echo "$issues" | jq 'length')`,
|
|
@@ -15518,9 +15589,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15518
15589
|
"",
|
|
15519
15590
|
"cmd_stale() {",
|
|
15520
15591
|
" # Check in-progress issues",
|
|
15592
|
+
" # (#823) cover all in-progress issues, not just the first 50",
|
|
15521
15593
|
" local ip_issues",
|
|
15522
15594
|
' ip_issues=$(gh issue list --label "status:in-progress" --state open \\',
|
|
15523
|
-
' --json number,title,updatedAt --limit
|
|
15595
|
+
' --json number,title,updatedAt --limit 1000 2>/dev/null || echo "[]")',
|
|
15524
15596
|
"",
|
|
15525
15597
|
" local ip_count",
|
|
15526
15598
|
` ip_count=$(echo "$ip_issues" | jq 'length')`,
|
|
@@ -15545,9 +15617,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15545
15617
|
" fi",
|
|
15546
15618
|
"",
|
|
15547
15619
|
" # Check blocked issues",
|
|
15620
|
+
" # (#823) cover all blocked issues, not just the first 50",
|
|
15548
15621
|
" local bl_issues",
|
|
15549
15622
|
' bl_issues=$(gh issue list --label "status:blocked" --state open \\',
|
|
15550
|
-
' --json number,title,updatedAt --limit
|
|
15623
|
+
' --json number,title,updatedAt,body --limit 1000 2>/dev/null || echo "[]")',
|
|
15551
15624
|
"",
|
|
15552
15625
|
" local bl_count",
|
|
15553
15626
|
` bl_count=$(echo "$bl_issues" | jq 'length')`,
|
|
@@ -15558,13 +15631,36 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15558
15631
|
' || date -u -d "${STALE_BLOCKED_HOURS} hours ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null)',
|
|
15559
15632
|
"",
|
|
15560
15633
|
" local bl_data",
|
|
15561
|
-
` bl_data=$(echo "$bl_issues" | jq -r '
|
|
15634
|
+
` bl_data=$(echo "$bl_issues" | jq -r '`,
|
|
15635
|
+
" .[] |",
|
|
15636
|
+
' (.body | split("\\n") | map(select(test("Depends on:"; "i"))) | .[0] // "") as $dep_line |',
|
|
15637
|
+
' "\\(.number)\\t\\(.title)\\t\\(.updatedAt)\\t\\($dep_line)"',
|
|
15638
|
+
" ')",
|
|
15562
15639
|
"",
|
|
15563
|
-
" while IFS=$'\\t' read -r num title updated; do",
|
|
15640
|
+
" while IFS=$'\\t' read -r num title updated dep_line; do",
|
|
15564
15641
|
' [[ -z "$num" ]] && continue',
|
|
15565
15642
|
' local updated_trimmed="${updated%%+*}"',
|
|
15566
15643
|
' updated_trimmed="${updated_trimmed%%Z*}"',
|
|
15567
15644
|
' if [[ "$updated_trimmed" < "$bl_threshold" ]]; then',
|
|
15645
|
+
" # (#822) Only flag a blocked issue with NO resolvable open dependency.",
|
|
15646
|
+
" # A parseable `Depends on: #N` naming any OPEN issue means the block is",
|
|
15647
|
+
" # legitimate phase-chain progress (the unblock sweep clears it when the",
|
|
15648
|
+
" # dep closes) \u2014 do NOT flag. Reserve the >7d flag for issues with no",
|
|
15649
|
+
" # `Depends on:` line, or whose deps are all closed yet still blocked.",
|
|
15650
|
+
' local deps=""',
|
|
15651
|
+
' [[ -n "$dep_line" ]] && deps=$(parse_deps "$dep_line")',
|
|
15652
|
+
" local has_open_dep=false",
|
|
15653
|
+
' if [[ -n "${deps// /}" ]]; then',
|
|
15654
|
+
" for dep in $deps; do",
|
|
15655
|
+
' if ! is_closed "$dep"; then',
|
|
15656
|
+
" has_open_dep=true",
|
|
15657
|
+
" break",
|
|
15658
|
+
" fi",
|
|
15659
|
+
" done",
|
|
15660
|
+
" fi",
|
|
15661
|
+
" if $has_open_dep; then",
|
|
15662
|
+
" continue",
|
|
15663
|
+
" fi",
|
|
15568
15664
|
' local date_part="${updated_trimmed%%T*}"',
|
|
15569
15665
|
' echo "STALE_BLOCKED #${num} \u2014 blocked since ${date_part} \u2014 \\"${title}\\""',
|
|
15570
15666
|
" fi",
|
|
@@ -15661,7 +15757,10 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15661
15757
|
" # `review:fixing` lease is left for that confirm pass to release,",
|
|
15662
15758
|
" # mirroring complete-feedback-handoff.sh.",
|
|
15663
15759
|
" #",
|
|
15664
|
-
" # Signals
|
|
15760
|
+
" # Signals \u2014 gathered by the shared `delegation_active_signals`",
|
|
15761
|
+
" # helper (defined once, embedded here AND in pr-sweep.sh's",
|
|
15762
|
+
" # cmd_needs_worker) so the reconcile and the feedback-drain judge a",
|
|
15763
|
+
" # delegation 'active' identically:",
|
|
15665
15764
|
" # - fix-list comment: the reviewer's most recent PR comment whose",
|
|
15666
15765
|
" # body contains the stable heading `## Reviewer: fix list for",
|
|
15667
15766
|
" # @issue-worker`. Its createdAt is the lease's reference time.",
|
|
@@ -15670,7 +15769,7 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15670
15769
|
" # `<!-- issue-worker feedback hand-off \u2014 review:needs-worker",
|
|
15671
15770
|
" # cleared -->` posted by complete-feedback-handoff.sh. Presence +",
|
|
15672
15771
|
" # createdAt newer than the fix-list means a result landed.",
|
|
15673
|
-
" # - branch HEAD: the PR head commit
|
|
15772
|
+
" # - branch HEAD: the PR head commit committer date, compared against",
|
|
15674
15773
|
" # the fix-list createdAt to detect a push that advanced past the",
|
|
15675
15774
|
" # delegation without a hand-off.",
|
|
15676
15775
|
"",
|
|
@@ -15707,24 +15806,14 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15707
15806
|
" while IFS=$'\\t' read -r pr_num has_marker head_oid; do",
|
|
15708
15807
|
' [[ -z "$pr_num" ]] && continue',
|
|
15709
15808
|
"",
|
|
15710
|
-
" #
|
|
15711
|
-
" #
|
|
15712
|
-
"
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
"",
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
` fixlist_at=$(echo "$comments" | jq -r '`,
|
|
15719
|
-
' [.[] | select(.body | contains("## Reviewer: fix list for @issue-worker"))]',
|
|
15720
|
-
` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
|
|
15721
|
-
"",
|
|
15722
|
-
" # Newest worker-report createdAt (empty if none found). Keyed off the",
|
|
15723
|
-
" # stable HTML marker complete-feedback-handoff.sh embeds.",
|
|
15724
|
-
" local report_at",
|
|
15725
|
-
` report_at=$(echo "$comments" | jq -r '`,
|
|
15726
|
-
' [.[] | select(.body | contains("issue-worker feedback hand-off \u2014 review:needs-worker cleared"))]',
|
|
15727
|
-
` | sort_by(.createdAt) | last | .createdAt // ""' 2>/dev/null)`,
|
|
15809
|
+
" # Gather the shared delegation-active signals in one call \u2014 the same",
|
|
15810
|
+
" # helper the feedback-drain (cmd_needs_worker) uses, so the two never",
|
|
15811
|
+
" # disagree on whether a delegation is demonstrably active. Yields the",
|
|
15812
|
+
" # fix-list reference time, the newest worker-report time, and the",
|
|
15813
|
+
" # branch HEAD committer date.",
|
|
15814
|
+
" local signals fixlist_at report_at head_at",
|
|
15815
|
+
' signals=$(delegation_active_signals "$pr_num" "$head_oid")',
|
|
15816
|
+
` IFS=$'\\x1f' read -r fixlist_at report_at head_at <<< "$signals"`,
|
|
15728
15817
|
"",
|
|
15729
15818
|
" # No fix-list comment at all \u2014 we cannot reason about this lease",
|
|
15730
15819
|
" # (e.g. a human applied `review:fixing` by hand). Leave it alone.",
|
|
@@ -15743,9 +15832,6 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15743
15832
|
" # Marker still set + no worker-report. Reconcile only when the",
|
|
15744
15833
|
" # branch advanced past the fix-list (the worker pushed but skipped",
|
|
15745
15834
|
" # the hand-off); otherwise this is a normal in-flight delegation.",
|
|
15746
|
-
" local head_at",
|
|
15747
|
-
' head_at=$(gh api "repos/{owner}/{repo}/commits/${head_oid}" \\',
|
|
15748
|
-
` --jq '.commit.committer.date // ""' 2>/dev/null || echo "")`,
|
|
15749
15835
|
' if [[ -n "$head_at" && "$head_at" > "$fixlist_at" ]]; then',
|
|
15750
15836
|
' if gh pr edit "$pr_num" --remove-label "review:needs-worker" >/dev/null 2>&1; then',
|
|
15751
15837
|
' gh pr comment "$pr_num" \\',
|
|
@@ -15819,6 +15905,16 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15819
15905
|
" # Extract the issue number after the '#' and before the ' '.",
|
|
15820
15906
|
" local num=${line#STALE #}",
|
|
15821
15907
|
" num=${num%% *}",
|
|
15908
|
+
" # (#822) Idempotent: never re-apply the human-clear-only flag to an",
|
|
15909
|
+
" # issue that already carries status:needs-attention.",
|
|
15910
|
+
" local existing_labels",
|
|
15911
|
+
' existing_labels=$(gh issue view "$num" --json labels \\',
|
|
15912
|
+
` --jq '.labels | map(.name) | index("status:needs-attention") != null' 2>/dev/null || echo "false")`,
|
|
15913
|
+
' if [[ "$existing_labels" == "true" ]]; then',
|
|
15914
|
+
' echo "SKIP_FLAGGED #${num} \u2014 already status:needs-attention (in-progress)"',
|
|
15915
|
+
' echo "$line"',
|
|
15916
|
+
" continue",
|
|
15917
|
+
" fi",
|
|
15822
15918
|
' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
|
|
15823
15919
|
' gh issue comment "$num" \\',
|
|
15824
15920
|
' --body "Flagged: in-progress for >3 days with no activity." >/dev/null 2>&1 || true',
|
|
@@ -15833,9 +15929,19 @@ function buildCheckBlockedScript(tiers, scopeGate, _runRatio) {
|
|
|
15833
15929
|
" 'STALE_BLOCKED #'*)",
|
|
15834
15930
|
" local num=${line#STALE_BLOCKED #}",
|
|
15835
15931
|
" num=${num%% *}",
|
|
15932
|
+
" # (#822) Idempotent: never re-apply the human-clear-only flag to an",
|
|
15933
|
+
" # issue that already carries status:needs-attention.",
|
|
15934
|
+
" local existing_labels",
|
|
15935
|
+
' existing_labels=$(gh issue view "$num" --json labels \\',
|
|
15936
|
+
` --jq '.labels | map(.name) | index("status:needs-attention") != null' 2>/dev/null || echo "false")`,
|
|
15937
|
+
' if [[ "$existing_labels" == "true" ]]; then',
|
|
15938
|
+
' echo "SKIP_FLAGGED #${num} \u2014 already status:needs-attention (blocked)"',
|
|
15939
|
+
' echo "$line"',
|
|
15940
|
+
" continue",
|
|
15941
|
+
" fi",
|
|
15836
15942
|
' if gh issue edit "$num" --add-label "status:needs-attention" >/dev/null 2>&1; then',
|
|
15837
15943
|
' gh issue comment "$num" \\',
|
|
15838
|
-
' --body "Flagged: blocked for >7 days \u2014 may need human intervention." >/dev/null 2>&1 || true',
|
|
15944
|
+
' --body "Flagged: blocked for >7 days with no resolvable dependency \u2014 may need human intervention." >/dev/null 2>&1 || true',
|
|
15839
15945
|
' echo "FLAGGED_BLOCKED #${num} \u2014 added status:needs-attention"',
|
|
15840
15946
|
" flagged_blocked_count=$((flagged_blocked_count + 1))",
|
|
15841
15947
|
" else",
|
|
@@ -16096,11 +16202,12 @@ function buildPrSweepScript() {
|
|
|
16096
16202
|
"# PR_ELIGIBLE / PR_SKIP line per open PR; the",
|
|
16097
16203
|
"# orchestrator dispatches `pr-reviewer` per eligible PR.",
|
|
16098
16204
|
"# needs-worker \u2014 Phase B1 feedback-drain. Emits one DRAIN line per",
|
|
16099
|
-
"# open PR carrying the `review:needs-worker` marker
|
|
16100
|
-
"# is NOT already in-flight (no `review:fixing` lease),",
|
|
16205
|
+
"# open PR carrying the `review:needs-worker` marker,",
|
|
16101
16206
|
"# ordered highest-priority-first so the orchestrator",
|
|
16102
16207
|
"# dispatches `issue-worker` per PR to consume the",
|
|
16103
|
-
"# reviewer's fix-list.",
|
|
16208
|
+
"# reviewer's fix-list. A PR that also holds the",
|
|
16209
|
+
"# `review:fixing` lease still drains unless a worker is",
|
|
16210
|
+
"# demonstrably active (see the needs-worker skip rules).",
|
|
16104
16211
|
"#",
|
|
16105
16212
|
"# Review-mode skip rules:",
|
|
16106
16213
|
"# - isDraft = true \u2192 reason=draft",
|
|
@@ -16108,10 +16215,21 @@ function buildPrSweepScript() {
|
|
|
16108
16215
|
"# - review:awaiting-human label \u2192 reason=awaiting-human",
|
|
16109
16216
|
"#",
|
|
16110
16217
|
"# Needs-worker-mode skip rules (PR carries review:needs-worker but is",
|
|
16111
|
-
"# not eligible to drain this cycle):",
|
|
16112
|
-
"#
|
|
16113
|
-
"#
|
|
16114
|
-
"#
|
|
16218
|
+
"# not eligible to drain this cycle). A PR with NO review:fixing lease",
|
|
16219
|
+
"# always drains \u2014 an idle marker means no worker has been dispatched.",
|
|
16220
|
+
"# A PR that ALSO carries review:fixing drains UNLESS a worker is",
|
|
16221
|
+
"# demonstrably active, judged by the SAME signals the lease-reconcile",
|
|
16222
|
+
"# uses (fix-list / worker-report / branch-HEAD timestamps):",
|
|
16223
|
+
"# - no reviewer fix-list comment \u2192 reason=no-fixlist",
|
|
16224
|
+
"# (nothing to drain \u2014 e.g. a hand-applied lease; be conservative)",
|
|
16225
|
+
"# - worker-report newer than the fix-list \u2192 reason=reported",
|
|
16226
|
+
"# (a worker result already landed \u2014 the reviewer confirm pass owns it)",
|
|
16227
|
+
"# - branch HEAD newer than the fix-list \u2192 reason=in-flight",
|
|
16228
|
+
"# (a worker pushed \u2014 actively fixing / consumed-uncleared; the",
|
|
16229
|
+
"# lease-reconcile owns it)",
|
|
16230
|
+
"# - otherwise (delegated but idle, no worker has touched it) \u2192 DRAIN.",
|
|
16231
|
+
"# This is the deadlock fix: the FIRST worker of a delegation is",
|
|
16232
|
+
"# dispatched instead of being skipped forever on the bare lease.",
|
|
16115
16233
|
"#",
|
|
16116
16234
|
"# Usage:",
|
|
16117
16235
|
"# .claude/procedures/pr-sweep.sh [review|needs-worker]",
|
|
@@ -16147,6 +16265,8 @@ function buildPrSweepScript() {
|
|
|
16147
16265
|
" esac",
|
|
16148
16266
|
"}",
|
|
16149
16267
|
"",
|
|
16268
|
+
...renderDelegationActiveSignalsHelper(),
|
|
16269
|
+
"",
|
|
16150
16270
|
"# \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",
|
|
16151
16271
|
"",
|
|
16152
16272
|
"cmd_review() {",
|
|
@@ -16181,15 +16301,22 @@ function buildPrSweepScript() {
|
|
|
16181
16301
|
"",
|
|
16182
16302
|
"cmd_needs_worker() {",
|
|
16183
16303
|
" # Phase B1 feedback-drain: enumerate open PRs carrying the durable",
|
|
16184
|
-
" # `review:needs-worker` marker
|
|
16185
|
-
" #
|
|
16186
|
-
" #
|
|
16187
|
-
" #
|
|
16188
|
-
" #
|
|
16189
|
-
" #
|
|
16304
|
+
" # `review:needs-worker` marker and dispatch `issue-worker` per PR to",
|
|
16305
|
+
" # consume the reviewer's fix-list, ordered highest-priority-first.",
|
|
16306
|
+
" #",
|
|
16307
|
+
" # A PR without the `review:fixing` lease is an idle marker \u2014 no worker",
|
|
16308
|
+
" # has been dispatched \u2014 so it DRAINs directly. A PR that ALSO carries",
|
|
16309
|
+
" # `review:fixing` is a delegation; it DRAINs UNLESS a worker is",
|
|
16310
|
+
" # demonstrably active, judged by the shared `delegation_active_signals`",
|
|
16311
|
+
" # helper (the SAME signals cmd_lease_reconcile uses). Crucially, the",
|
|
16312
|
+
" # bare presence of `review:fixing` no longer skips: the reviewer",
|
|
16313
|
+
" # acquires the lease and applies the marker in ONE atomic pass and",
|
|
16314
|
+
" # then holds the lease across sessions, so a lease with no worker",
|
|
16315
|
+
" # activity yet is a delegated-but-idle PR whose FIRST worker must be",
|
|
16316
|
+
" # dispatched \u2014 skipping it on the bare lease wedged the PR forever.",
|
|
16190
16317
|
" local prs",
|
|
16191
16318
|
" prs=$(gh pr list --state open --label 'review:needs-worker' \\",
|
|
16192
|
-
' --json number,labels --limit 50 2>/dev/null || echo "[]")',
|
|
16319
|
+
' --json number,labels,headRefOid --limit 50 2>/dev/null || echo "[]")',
|
|
16193
16320
|
"",
|
|
16194
16321
|
" local count",
|
|
16195
16322
|
` count=$(echo "$prs" | jq 'length')`,
|
|
@@ -16198,34 +16325,65 @@ function buildPrSweepScript() {
|
|
|
16198
16325
|
" return 0",
|
|
16199
16326
|
" fi",
|
|
16200
16327
|
"",
|
|
16201
|
-
" # First pass:
|
|
16202
|
-
" #
|
|
16203
|
-
" #
|
|
16204
|
-
" #
|
|
16328
|
+
" # First pass: classify each PR. A PR with no `review:fixing` lease is",
|
|
16329
|
+
" # an idle marker \u2192 DRAIN directly (head-oid unused). A PR that also",
|
|
16330
|
+
" # carries `review:fixing` is a CANDIDATE \u2014 the demonstrably-active",
|
|
16331
|
+
" # predicate is applied per-PR in the loop below (it needs comment /",
|
|
16332
|
+
" # commit reads, so it cannot live in jq). Emit `kind\\tnumber\\thead`.",
|
|
16205
16333
|
" local candidates",
|
|
16206
16334
|
` candidates=$(echo "$prs" | jq -r '`,
|
|
16207
16335
|
" sort_by(.number) |",
|
|
16208
16336
|
" .[] |",
|
|
16209
16337
|
" (.labels | map(.name)) as $labels |",
|
|
16210
16338
|
' if ($labels | index("review:fixing")) then',
|
|
16211
|
-
' "
|
|
16339
|
+
' "CANDIDATE\\t\\(.number)\\t\\(.headRefOid)"',
|
|
16212
16340
|
" else",
|
|
16213
|
-
' "DRAIN\\t\\(.number)"',
|
|
16341
|
+
' "DRAIN\\t\\(.number)\\t"',
|
|
16214
16342
|
" end",
|
|
16215
16343
|
" ')",
|
|
16216
16344
|
"",
|
|
16217
16345
|
' local drain_rows=""',
|
|
16218
16346
|
' local emitted_any=""',
|
|
16219
|
-
" while IFS=$'\\t' read -r kind pr_num; do",
|
|
16347
|
+
" while IFS=$'\\t' read -r kind pr_num head_oid; do",
|
|
16220
16348
|
' [[ -z "$pr_num" ]] && continue',
|
|
16221
|
-
|
|
16222
|
-
'
|
|
16223
|
-
|
|
16224
|
-
"
|
|
16349
|
+
"",
|
|
16350
|
+
' if [[ "$kind" == "CANDIDATE" ]]; then',
|
|
16351
|
+
" # `review:fixing` present \u2014 drain UNLESS a worker is demonstrably",
|
|
16352
|
+
" # active. Mirror cmd_lease_reconcile's skip conditions exactly so",
|
|
16353
|
+
" # the drain and the reconcile never diverge on what 'active' means.",
|
|
16354
|
+
" local signals fixlist_at report_at head_at",
|
|
16355
|
+
' signals=$(delegation_active_signals "$pr_num" "$head_oid")',
|
|
16356
|
+
` IFS=$'\\x1f' read -r fixlist_at report_at head_at <<< "$signals"`,
|
|
16357
|
+
"",
|
|
16358
|
+
' if [[ -z "$fixlist_at" ]]; then',
|
|
16359
|
+
" # No reviewer fix-list (e.g. a hand-applied lease). Nothing to",
|
|
16360
|
+
" # drain \u2014 be conservative and leave it for a human / reconcile.",
|
|
16361
|
+
' echo "PR_SKIP #${pr_num} reason=no-fixlist"',
|
|
16362
|
+
' emitted_any="1"',
|
|
16363
|
+
" continue",
|
|
16364
|
+
" fi",
|
|
16365
|
+
' if [[ -n "$report_at" && "$report_at" > "$fixlist_at" ]]; then',
|
|
16366
|
+
" # A worker result already landed \u2014 the reviewer confirm pass",
|
|
16367
|
+
" # owns this PR now, not the drain.",
|
|
16368
|
+
' echo "PR_SKIP #${pr_num} reason=reported"',
|
|
16369
|
+
' emitted_any="1"',
|
|
16370
|
+
" continue",
|
|
16371
|
+
" fi",
|
|
16372
|
+
' if [[ -n "$head_at" && "$head_at" > "$fixlist_at" ]]; then',
|
|
16373
|
+
" # Branch advanced past the fix-list \u2192 a worker pushed. Actively",
|
|
16374
|
+
" # fixing (or consumed-uncleared); the lease-reconcile owns it.",
|
|
16375
|
+
' echo "PR_SKIP #${pr_num} reason=in-flight"',
|
|
16376
|
+
' emitted_any="1"',
|
|
16377
|
+
" continue",
|
|
16378
|
+
" fi",
|
|
16379
|
+
" # else: delegated but idle \u2014 no worker has touched it. Fall",
|
|
16380
|
+
" # through to DRAIN. THIS is the deadlock fix.",
|
|
16225
16381
|
" fi",
|
|
16226
|
-
"
|
|
16227
|
-
" #
|
|
16228
|
-
" #
|
|
16382
|
+
"",
|
|
16383
|
+
" # DRAIN (idle marker, or a delegated-but-idle candidate that fell",
|
|
16384
|
+
" # through) \u2014 resolve the linked issue's priority so the drain order",
|
|
16385
|
+
" # is highest-priority-first, tie-broken by PR number ascending",
|
|
16386
|
+
" # (FIFO, mirroring the queue scan's number-asc tie-break).",
|
|
16229
16387
|
" local pkey",
|
|
16230
16388
|
' pkey=$(priority_key_for_pr "$pr_num")',
|
|
16231
16389
|
' drain_rows="${drain_rows}${pkey}\\t${pr_num}\\n"',
|
|
@@ -16263,7 +16421,7 @@ function buildPrSweepScript() {
|
|
|
16263
16421
|
function buildPrSweepProcedure() {
|
|
16264
16422
|
return {
|
|
16265
16423
|
name: "pr-sweep.sh",
|
|
16266
|
-
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
|
|
16424
|
+
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.",
|
|
16267
16425
|
content: buildPrSweepScript()
|
|
16268
16426
|
};
|
|
16269
16427
|
}
|
|
@@ -16544,19 +16702,27 @@ var orchestratorSubAgent = {
|
|
|
16544
16702
|
"",
|
|
16545
16703
|
"```",
|
|
16546
16704
|
"PR_DRAIN #<n>",
|
|
16547
|
-
"PR_SKIP #<n> reason
|
|
16705
|
+
"PR_SKIP #<n> reason=<no-fixlist|reported|in-flight>",
|
|
16548
16706
|
"```",
|
|
16549
16707
|
"",
|
|
16550
16708
|
"The procedure enumerates every open PR carrying the",
|
|
16551
16709
|
"`review:needs-worker` marker, ordered **highest-priority-first**",
|
|
16552
|
-
"(linked-issue `priority:*`, tie-broken by PR number ascending).
|
|
16553
|
-
"
|
|
16554
|
-
"`review:fixing` lease
|
|
16555
|
-
"
|
|
16556
|
-
"
|
|
16557
|
-
"
|
|
16558
|
-
"
|
|
16559
|
-
"
|
|
16710
|
+
"(linked-issue `priority:*`, tie-broken by PR number ascending). A PR",
|
|
16711
|
+
"with an idle marker (no `review:fixing` lease) always drains. A PR",
|
|
16712
|
+
"that **also** carries the `review:fixing` lease still drains **unless",
|
|
16713
|
+
"a worker is demonstrably active** \u2014 the reviewer acquires the lease",
|
|
16714
|
+
"and applies the marker in one atomic pass and then holds the lease",
|
|
16715
|
+
"across sessions, so the bare lease does **not** mean a worker is",
|
|
16716
|
+
"running. The procedure judges 'active' with the same",
|
|
16717
|
+
"`delegation_active_signals` helper the maintenance lease-reconcile",
|
|
16718
|
+
"uses, skipping only when: there is no reviewer fix-list comment",
|
|
16719
|
+
"(`reason=no-fixlist`), a worker-report already landed newer than the",
|
|
16720
|
+
"fix-list (`reason=reported`), or the branch HEAD advanced past the",
|
|
16721
|
+
"fix-list (`reason=in-flight`). A delegated-but-idle PR \u2014 lease held",
|
|
16722
|
+
"but no worker has touched it \u2014 **drains**, so its FIRST worker gets",
|
|
16723
|
+
"dispatched instead of the PR wedging forever. The phase stays",
|
|
16724
|
+
"**idempotent**: a PR actively being worked, or one whose worker",
|
|
16725
|
+
"already reported, never appears as a `PR_DRAIN` line.",
|
|
16560
16726
|
"",
|
|
16561
16727
|
"If the script emits `NO_NEEDS_WORKER_PRS`, log the empty result and",
|
|
16562
16728
|
"skip directly to Phase C.",
|
|
@@ -16908,9 +17074,12 @@ var orchestratorSubAgent = {
|
|
|
16908
17074
|
" PR (highest-priority first) to consume the reviewer's fix-list,",
|
|
16909
17075
|
" and loops until the queue is empty \u2014 feedback PRs are preferred",
|
|
16910
17076
|
" over fresh issues, so Phase B1 runs before Phase E / Phase G.",
|
|
16911
|
-
"
|
|
16912
|
-
"
|
|
16913
|
-
"
|
|
17077
|
+
" A PR that also carries the `review:fixing` lease still drains",
|
|
17078
|
+
" **unless a worker is demonstrably active** (the procedure skips",
|
|
17079
|
+
" it as `reason=no-fixlist` / `reason=reported` / `reason=in-flight`",
|
|
17080
|
+
" via the shared active-signals check) \u2014 a delegated-but-idle PR",
|
|
17081
|
+
" holding the bare lease drains so its first worker is dispatched.",
|
|
17082
|
+
" A single worker's failure never stops the drain; the next cycle",
|
|
16914
17083
|
" re-dispatches against any PR whose marker is still unconsumed."
|
|
16915
17084
|
].join("\n")
|
|
16916
17085
|
};
|
|
@@ -17613,8 +17782,8 @@ var ORCHESTRATOR_CONVENTIONS_PREAMBLE = [
|
|
|
17613
17782
|
"",
|
|
17614
17783
|
"- 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",
|
|
17615
17784
|
"- 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`).",
|
|
17616
|
-
"- **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
|
|
17617
|
-
"- 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
|
|
17785
|
+
"- **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.",
|
|
17786
|
+
"- 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.",
|
|
17618
17787
|
"- 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.",
|
|
17619
17788
|
"- 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.",
|
|
17620
17789
|
"- Stale thresholds: 72h for in-progress, 168h for blocked",
|
|
@@ -22354,11 +22523,47 @@ function buildRegulatoryResearchAnalystSubAgent(paths, issueDefaults) {
|
|
|
22354
22523
|
"",
|
|
22355
22524
|
" ## Traceability",
|
|
22356
22525
|
" - **Source regulation:** <citation>",
|
|
22357
|
-
" - **Requirements created:** #<N>, #<N> (SEC req:scan issues)",
|
|
22358
22526
|
" - **Affected segments:** <relative links>",
|
|
22359
22527
|
" - **Standards implementing:** <relative links> (if applicable)",
|
|
22528
|
+
"",
|
|
22529
|
+
" **Requirements created** (one per line, ascending issue-number",
|
|
22530
|
+
" order \u2014 append your own entries only):",
|
|
22531
|
+
"",
|
|
22532
|
+
" - #<N> \u2014 <PREFIX> req:scan",
|
|
22360
22533
|
" ```",
|
|
22361
22534
|
"",
|
|
22535
|
+
" The **Requirements created** block is an **append-only,",
|
|
22536
|
+
" one-issue-per-line, deterministically sorted** list \u2014 it is a",
|
|
22537
|
+
" merge-conflict hotspot because sibling `regulatory:impact`",
|
|
22538
|
+
" sessions land requirement entries into the same regulation page",
|
|
22539
|
+
" concurrently. Maintain it under the following discipline (the",
|
|
22540
|
+
" same discipline the `shared-editing-safety` rule prescribes for",
|
|
22541
|
+
" shared registries, applied here to this one list section \u2014 the",
|
|
22542
|
+
" surrounding regulation page is a hand-authored document, not a",
|
|
22543
|
+
" registry, so only this section is governed):",
|
|
22544
|
+
"",
|
|
22545
|
+
" - **One entry per line.** Write each requirement you created as",
|
|
22546
|
+
" its own new `- #<N> \u2014 <PREFIX> req:scan` bullet. Never collapse",
|
|
22547
|
+
" entries into a single comma-separated",
|
|
22548
|
+
" `**Requirements created:** #N, #M` line \u2014 that single mutable",
|
|
22549
|
+
" line is the original conflict cause and always conflicts when",
|
|
22550
|
+
" two sessions touch it.",
|
|
22551
|
+
" - **Ascending issue-number order.** Insert your bullet in the",
|
|
22552
|
+
" position that keeps the list sorted by issue number. A",
|
|
22553
|
+
" single-line bullet insert into a sorted list rarely truly",
|
|
22554
|
+
" conflicts, so git auto-merges concurrent sibling inserts.",
|
|
22555
|
+
" - **Append-only.** Never rewrite, reorder, or merge existing",
|
|
22556
|
+
" bullets. If a legacy comma line",
|
|
22557
|
+
" (`**Requirements created:** #N, #M`) already exists on the",
|
|
22558
|
+
" page, leave it untouched and add your entries as per-line",
|
|
22559
|
+
" bullets below it \u2014 do **not** rewrite the legacy line, because",
|
|
22560
|
+
" rewriting it is itself a conflict.",
|
|
22561
|
+
" - **Defer the commit of this section to the final pre-push",
|
|
22562
|
+
" step.** Pull the latest default branch before inserting, insert",
|
|
22563
|
+
" only your own entry, and if a concurrent sibling already landed",
|
|
22564
|
+
" a bullet, rebase and re-insert. Commit this shared list section",
|
|
22565
|
+
" on its own at the end, then push immediately.",
|
|
22566
|
+
"",
|
|
22362
22567
|
"4. **Create `req:scan` issues** for each capability gap identified.",
|
|
22363
22568
|
" Each issue hands off to the `requirements-analyst` bundle so its",
|
|
22364
22569
|
" scan phase can deduplicate and open `req:write` issues in the",
|
|
@@ -22901,6 +23106,13 @@ var REQ_WRITE_ISSUE_SCHEMA_SECTION = [
|
|
|
22901
23106
|
"",
|
|
22902
23107
|
"### Strongly Recommended",
|
|
22903
23108
|
"",
|
|
23109
|
+
"- **Requirement ID:** the reserved requirement ID (e.g.",
|
|
23110
|
+
" `FR-1240`), a HINT allocated at issue-creation. Optional \u2014",
|
|
23111
|
+
" legacy issues may omit it. The writer re-validates and finalizes",
|
|
23112
|
+
" the ID at write-start (via `next-requirement-id.sh`) before",
|
|
23113
|
+
" authoring, so this field is advisory: if a concurrent claim took",
|
|
23114
|
+
" the reserved number, the writer authors under the freshly",
|
|
23115
|
+
" finalized ID instead and updates this field to match.",
|
|
22904
23116
|
"- **Inputs / Read:** bullet list of source files the writer should",
|
|
22905
23117
|
" consult \u2014 the upstream proposal file (when one exists), the",
|
|
22906
23118
|
" BCM/source document(s) the proposal cites, and any related",
|
|
@@ -22925,518 +23137,6 @@ var REQ_WRITE_ISSUE_SCHEMA_SECTION = [
|
|
|
22925
23137
|
"the label to `status:ready` before the writer picks it up."
|
|
22926
23138
|
];
|
|
22927
23139
|
|
|
22928
|
-
// src/agent/bundles/requirements-analyst.ts
|
|
22929
|
-
function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
|
|
22930
|
-
return {
|
|
22931
|
-
name: "requirements-analyst",
|
|
22932
|
-
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.",
|
|
22933
|
-
model: AGENT_MODEL.POWERFUL,
|
|
22934
|
-
maxTurns: 80,
|
|
22935
|
-
platforms: { cursor: { exclude: true } },
|
|
22936
|
-
prompt: [
|
|
22937
|
-
"# Requirements Analyst Agent",
|
|
22938
|
-
"",
|
|
22939
|
-
"Dedicated agent loop for discovering requirement gaps from BCM (Business",
|
|
22940
|
-
"Capability Model) documents, product docs, and competitive analysis \u2014 then",
|
|
22941
|
-
"creating well-formed requirement issues for the downstream",
|
|
22942
|
-
"`requirements-writer` agent to draft. Designed for scheduled execution",
|
|
22943
|
-
"downstream of the BCM writer and company research agents.",
|
|
22944
|
-
"",
|
|
22945
|
-
"Follow your project's shared agent conventions (`AGENTS.md`,",
|
|
22946
|
-
"`CLAUDE.md`, or equivalent) for all commit, branch, and PR rules.",
|
|
22947
|
-
"",
|
|
22948
|
-
"---",
|
|
22949
|
-
"",
|
|
22950
|
-
...PROJECT_CONTEXT_MAINTAINER_SECTION,
|
|
22951
|
-
"## Design Principles",
|
|
22952
|
-
"",
|
|
22953
|
-
"1. **Discover, don't write.** This agent identifies *what requirements are",
|
|
22954
|
-
" missing*. The `requirements-writer` agent writes the actual documents.",
|
|
22955
|
-
" The boundary keeps this agent fast and the `requirements-writer`",
|
|
22956
|
-
" authoritative.",
|
|
22957
|
-
"2. **Trace everything.** Every discovered gap links to the source that",
|
|
22958
|
-
" revealed it (a BCM model doc, competitive analysis, product doc, or",
|
|
22959
|
-
" meeting extract).",
|
|
22960
|
-
"3. **Respect the taxonomy.** Route every discovered requirement to the",
|
|
22961
|
-
" correct BCM category (FR, BR, NFR, SEC, DR, INT, OPS, UX, MT, ADR, TR)",
|
|
22962
|
-
" using the shared disambiguation rules \u2014 the same taxonomy the",
|
|
22963
|
-
" `requirements-writer` and `requirements-reviewer` load. The",
|
|
22964
|
-
" canonical source lives in `requirements-taxonomy.ts` and is",
|
|
22965
|
-
" embedded verbatim in both downstream agents' prompts.",
|
|
22966
|
-
"4. **Deduplicate.** Before creating an issue, check whether a requirement",
|
|
22967
|
-
" already exists or an issue is already open for it.",
|
|
22968
|
-
"",
|
|
22969
|
-
"---",
|
|
22970
|
-
"",
|
|
22971
|
-
"## State Machine Overview",
|
|
22972
|
-
"",
|
|
22973
|
-
"Requirements synthesis flows through **2 phases**:",
|
|
22974
|
-
"",
|
|
22975
|
-
"```",
|
|
22976
|
-
"\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",
|
|
22977
|
-
"\u2502 1. SCAN \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 2. DRAFT-TRACE \u2502",
|
|
22978
|
-
"\u2502 Read docs, \u2502 \u2502 Write proposals,\u2502",
|
|
22979
|
-
"\u2502 identify \u2502 \u2502 create req:write\u2502",
|
|
22980
|
-
"\u2502 gaps, check \u2502 \u2502 issues, update \u2502",
|
|
22981
|
-
"\u2502 for dupes \u2502 \u2502 source docs \u2502",
|
|
22982
|
-
"\u2502 \u2502 \u2502 with traceability\u2502",
|
|
22983
|
-
"\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",
|
|
22984
|
-
"```",
|
|
22985
|
-
"",
|
|
22986
|
-
"This pipeline matches the `scan \u2192 draft-trace \u2192 write` pattern",
|
|
22987
|
-
"used by the sibling openhi project. Draft and Trace were previously",
|
|
22988
|
-
"separate phases; they were collapsed because the proposals file Draft",
|
|
22989
|
-
"wrote was only ever consumed by Trace (no human review, no async work",
|
|
22990
|
-
"between the two), so the phase boundary added latency without value.",
|
|
22991
|
-
"See ADR-009 for the full reasoning.",
|
|
22992
|
-
"",
|
|
22993
|
-
"**Issue labels encode the phase:**",
|
|
22994
|
-
"",
|
|
22995
|
-
"| Label | Phase | Session work |",
|
|
22996
|
-
"|-------|-------|-------------|",
|
|
22997
|
-
"| `req:scan` | 1. Scan | Read source docs, identify potential requirement gaps, check against existing requirements and open issues, write deduplicated scan report |",
|
|
22998
|
-
"| `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 |",
|
|
22999
|
-
"",
|
|
23000
|
-
"All issues also carry `type:requirement` and a `status:*` label.",
|
|
23001
|
-
"",
|
|
23002
|
-
"**Issue count per scan cycle:** 1 scan + 1 draft-trace = **2 sessions**.",
|
|
23003
|
-
"",
|
|
23004
|
-
"**Shortened paths:**",
|
|
23005
|
-
"- No gaps found after scan \u2192 skip draft-trace \u2192 **1 session**",
|
|
23006
|
-
"",
|
|
23007
|
-
"---",
|
|
23008
|
-
"",
|
|
23009
|
-
"## Configurable Paths",
|
|
23010
|
-
"",
|
|
23011
|
-
"Projects adopting this bundle must define these paths in their agent",
|
|
23012
|
-
"configuration (`agentConfig.rules` extension or project-level docs):",
|
|
23013
|
-
"",
|
|
23014
|
-
"| Placeholder | Meaning | Typical value |",
|
|
23015
|
-
"|-------------|---------|---------------|",
|
|
23016
|
-
`| \`<BCM_DOCS_ROOT>\` | Root of BCM model docs (capability models) | \`${paths.bcmRoot}/\` |`,
|
|
23017
|
-
`| \`<COMPETITIVE_ROOT>\` | Competitive analysis docs | \`${paths.docsRoot}/business-strategy/competitive/\` |`,
|
|
23018
|
-
`| \`<PRODUCT_ROOT>\` | Product roadmap / entity taxonomy | \`${paths.docsRoot}/product/\` |`,
|
|
23019
|
-
`| \`<MEETINGS_ROOT>\` | Meeting extracts | \`${paths.docsRoot}/research/meetings/\` |`,
|
|
23020
|
-
`| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Scan reports and proposals | \`${paths.researchRequirementsRoot}/\` |`,
|
|
23021
|
-
`| \`<REQUIREMENTS_ROOT>\` | Final requirement documents (owned by requirements-writer) | \`${paths.requirementsRoot}/\` |`,
|
|
23022
|
-
"| `<PREFIX>` | Project-specific requirement ID prefix | e.g. `VRTX`, `ACME` |",
|
|
23023
|
-
"",
|
|
23024
|
-
"If your project stores these in different locations, substitute accordingly",
|
|
23025
|
-
"wherever the phase instructions reference a path.",
|
|
23026
|
-
"",
|
|
23027
|
-
"---",
|
|
23028
|
-
"",
|
|
23029
|
-
"## Agent Loop",
|
|
23030
|
-
"",
|
|
23031
|
-
"Run this loop exactly once per session. Never start a second issue.",
|
|
23032
|
-
"",
|
|
23033
|
-
"1. Claim one open `type:requirement` issue using phase priority:",
|
|
23034
|
-
" `req:scan` > `req:draft-trace`.",
|
|
23035
|
-
"2. Transition `status:ready` \u2192 `status:in-progress` and create the branch",
|
|
23036
|
-
" per your project's branch-naming convention.",
|
|
23037
|
-
"3. Execute the phase handler that matches the issue's `req:*` label.",
|
|
23038
|
-
"4. Commit, push, open a PR (if applicable), and close the issue per your",
|
|
23039
|
-
" project's PR workflow.",
|
|
23040
|
-
"",
|
|
23041
|
-
"---",
|
|
23042
|
-
"",
|
|
23043
|
-
"## Phase 1: Scan (`req:scan`)",
|
|
23044
|
-
"",
|
|
23045
|
-
"**Goal:** Read source documents, identify where requirements are missing,",
|
|
23046
|
-
"incomplete, or contradictory, then check each potential gap against existing",
|
|
23047
|
-
"requirements and open issues to eliminate duplicates. Produces a single",
|
|
23048
|
-
"deduplicated scan report.",
|
|
23049
|
-
"",
|
|
23050
|
-
"**Budget:** Reading source docs + reading requirement registries + searching",
|
|
23051
|
-
"issues. Write one deduplicated scan output file.",
|
|
23052
|
-
"",
|
|
23053
|
-
"### Scan Sources",
|
|
23054
|
-
"",
|
|
23055
|
-
"The issue specifies which source(s) to scan. Common scan scopes:",
|
|
23056
|
-
"",
|
|
23057
|
-
"| Scope | What to read | What to look for |",
|
|
23058
|
-
"|-------|-------------|-----------------|",
|
|
23059
|
-
`| **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. |`,
|
|
23060
|
-
"| **Competitive analysis** | One `comp-*.md` doc under `<COMPETITIVE_ROOT>` | Feature comparison gaps \u2014 competitor features the product lacks requirements for |",
|
|
23061
|
-
"| **Product roadmap** | `<PRODUCT_ROOT>/prioritized-feature-roadmap.md` | Roadmap items without corresponding FRs |",
|
|
23062
|
-
"| **Entity taxonomy** | `<PRODUCT_ROOT>/entity-taxonomy.md` | Entities without CRUD requirements (FR), data requirements (DR), or security requirements (SEC) |",
|
|
23063
|
-
"| **Meeting extract** | `<MEETINGS_ROOT>/meeting-*.extract.md` | Requirements identified but not yet formalized |",
|
|
23064
|
-
"",
|
|
23065
|
-
"### Steps",
|
|
23066
|
-
"",
|
|
23067
|
-
"1. **Read the source documents** specified in the issue.",
|
|
23068
|
-
"",
|
|
23069
|
-
"2. **Identify potential gaps.** For each potential missing requirement:",
|
|
23070
|
-
" - Classify into the correct BCM category (FR, BR, NFR, SEC, DR, INT,",
|
|
23071
|
-
" OPS, UX, MT, ADR, TR)",
|
|
23072
|
-
" - Apply the shared disambiguation rules (from `requirements-taxonomy.ts`,",
|
|
23073
|
-
" also embedded in the requirements-writer and requirements-reviewer",
|
|
23074
|
-
" prompts)",
|
|
23075
|
-
" - Note the source that revealed the gap",
|
|
23076
|
-
" - Estimate priority based on the source context",
|
|
23077
|
-
"",
|
|
23078
|
-
"3. **Read the requirements registry.** Scan `_index.md` files in each",
|
|
23079
|
-
" `<REQUIREMENTS_ROOT>/<category>/` directory to know what already exists.",
|
|
23080
|
-
"",
|
|
23081
|
-
"4. **Search for existing issues.** For each potential gap, search open issues:",
|
|
23082
|
-
" ```bash",
|
|
23083
|
-
' gh issue list --label "type:requirement" --state open \\',
|
|
23084
|
-
" --json number,title --limit 100",
|
|
23085
|
-
" ```",
|
|
23086
|
-
"",
|
|
23087
|
-
"5. **Classify each gap:**",
|
|
23088
|
-
" - **New** \u2014 no existing requirement or open issue covers this",
|
|
23089
|
-
" - **Duplicate** \u2014 an existing requirement already addresses this",
|
|
23090
|
-
" - **In progress** \u2014 an open issue already targets this",
|
|
23091
|
-
" - **Partial** \u2014 existing requirement partially covers this; note the gap",
|
|
23092
|
-
"",
|
|
23093
|
-
"6. **Write the deduplicated scan report** to:",
|
|
23094
|
-
" ```",
|
|
23095
|
-
" <RESEARCH_REQUIREMENTS_ROOT>/req-scan-<scope>-<YYYY-MM-DD>.md",
|
|
23096
|
-
" ```",
|
|
23097
|
-
"",
|
|
23098
|
-
" Format:",
|
|
23099
|
-
" ```markdown",
|
|
23100
|
-
" ---",
|
|
23101
|
-
' title: "Requirements Scan: <scope>"',
|
|
23102
|
-
" date: YYYY-MM-DD",
|
|
23103
|
-
" parent_issue: <N>",
|
|
23104
|
-
" status: complete",
|
|
23105
|
-
" ---",
|
|
23106
|
-
"",
|
|
23107
|
-
" ## Source Documents Reviewed",
|
|
23108
|
-
" - <path> \u2014 <brief description>",
|
|
23109
|
-
"",
|
|
23110
|
-
" ## Existing Requirements Checked",
|
|
23111
|
-
" - <category>: <count> existing docs, <count> open issues",
|
|
23112
|
-
"",
|
|
23113
|
-
" ## Identified Gaps (New)",
|
|
23114
|
-
" ### Gap 1: <Title>",
|
|
23115
|
-
" - **Category:** FR / BR / NFR / SEC / DR / INT / OPS / UX / MT / ADR / TR",
|
|
23116
|
-
" - **Source:** <path to doc + section that revealed this gap>",
|
|
23117
|
-
" - **Priority:** High / Normal / Low",
|
|
23118
|
-
" - **Rationale:** <why this requirement is needed>",
|
|
23119
|
-
" - **Duplicate check:** No existing requirement or open issue found",
|
|
23120
|
-
" - **Proposed scope:** <1-2 sentences on what the requirement should cover>",
|
|
23121
|
-
"",
|
|
23122
|
-
" ## Already Covered",
|
|
23123
|
-
" <list of potential gaps that turned out to already have requirements>",
|
|
23124
|
-
"",
|
|
23125
|
-
" ## In Progress",
|
|
23126
|
-
" <gaps that already have open issues \u2014 include issue numbers>",
|
|
23127
|
-
"",
|
|
23128
|
-
" ## Ambiguous / Needs Human Decision",
|
|
23129
|
-
" <gaps where the correct category or scope is unclear>",
|
|
23130
|
-
" ```",
|
|
23131
|
-
"",
|
|
23132
|
-
"7. **Create downstream issues based on findings:**",
|
|
23133
|
-
" - If any new gaps were identified \u2192 create `req:draft-trace` issue",
|
|
23134
|
-
" (blocked on this issue via `Depends on: #N`).",
|
|
23135
|
-
" - If **no gaps** were found \u2192 stop (no further phases needed). Comment",
|
|
23136
|
-
" on the issue noting that no gaps were identified, and proceed directly",
|
|
23137
|
-
" to commit and push. The scan issue will be marked done with no",
|
|
23138
|
-
" downstream work needed.",
|
|
23139
|
-
"",
|
|
23140
|
-
"8. **Commit and push.**",
|
|
23141
|
-
"",
|
|
23142
|
-
"---",
|
|
23143
|
-
"",
|
|
23144
|
-
"## Phase 2: Draft & Trace (`req:draft-trace`)",
|
|
23145
|
-
"",
|
|
23146
|
-
"**Goal:** Expand each identified gap into a requirement proposal, create",
|
|
23147
|
-
"a `req:write` GitHub issue for each proposal so the downstream",
|
|
23148
|
-
"`requirements-writer` bundle picks it up, and backfill source-document",
|
|
23149
|
-
"traceability notes \u2014 all in a single session.",
|
|
23150
|
-
"",
|
|
23151
|
-
"**Budget:** No web searches. Reading + writing proposals + issue creation",
|
|
23152
|
-
"+ minor traceability edits to source documents.",
|
|
23153
|
-
"",
|
|
23154
|
-
"Draft and Trace were previously separate phases. They were collapsed",
|
|
23155
|
-
"because the proposals file written by Draft was only ever consumed by",
|
|
23156
|
-
"Trace \u2014 no human review, no async work, no CI validation sat between",
|
|
23157
|
-
"them. See ADR-009 for the decision record.",
|
|
23158
|
-
"",
|
|
23159
|
-
"### `req:write` issue schema",
|
|
23160
|
-
"",
|
|
23161
|
-
"The `req:write` issues this phase creates are picked up by the",
|
|
23162
|
-
"downstream `requirements-writer` agent, which parses a strict",
|
|
23163
|
-
"schema on intake. The authoritative schema is defined in the",
|
|
23164
|
-
"`requirements-writer` sub-agent prompt under **The `req:write`",
|
|
23165
|
-
"Issue Schema** \u2014 every `req:write` issue this phase opens must",
|
|
23166
|
-
"conform. The same schema is embedded in the",
|
|
23167
|
-
"`requirements-reviewer` follow-up generator so the three",
|
|
23168
|
-
"producers never drift apart.",
|
|
23169
|
-
"",
|
|
23170
|
-
...REQ_WRITE_ISSUE_SCHEMA_SECTION,
|
|
23171
|
-
"",
|
|
23172
|
-
"**Deriving the three required fields from the proposal.** For",
|
|
23173
|
-
"every `req:write` issue this phase opens, the three fields come",
|
|
23174
|
-
"directly from the proposal entry written earlier in the same",
|
|
23175
|
-
"session:",
|
|
23176
|
-
"",
|
|
23177
|
-
"- **Category** \u2014 the proposal's `**Category:**` line.",
|
|
23178
|
-
"- **Tier** \u2014 the proposal's `**Tier:**` line.",
|
|
23179
|
-
"- **Output Path** \u2014",
|
|
23180
|
-
" `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`,",
|
|
23181
|
-
" using the sequence number determined in the proposal step below.",
|
|
23182
|
-
"",
|
|
23183
|
-
"**Validation step \u2014 required before opening the issue.** Before",
|
|
23184
|
-
"calling `gh issue create`, verify all three required fields are",
|
|
23185
|
-
"populated and internally consistent (Category matches title",
|
|
23186
|
-
"prefix, Tier matches `tier:*` label, Output Path filename starts",
|
|
23187
|
-
"with the Category prefix). If any field cannot be derived, open",
|
|
23188
|
-
"the issue with `status:needs-attention` (not `status:ready`) and",
|
|
23189
|
-
"include a `Missing: <field> \u2014 <one-line reason>` line so a human",
|
|
23190
|
-
"triaging the issue only has to supply the remaining value(s)",
|
|
23191
|
-
"before flipping the label to `status:ready`.",
|
|
23192
|
-
"",
|
|
23193
|
-
"### Steps",
|
|
23194
|
-
"",
|
|
23195
|
-
"1. **Read the scan report** from Phase 1.",
|
|
23196
|
-
"",
|
|
23197
|
-
"2. **For each gap**, write a detailed proposal:",
|
|
23198
|
-
"",
|
|
23199
|
-
" ```markdown",
|
|
23200
|
-
" ## Proposed: <PREFIX>-<NNN> \u2014 <Title>",
|
|
23201
|
-
"",
|
|
23202
|
-
" **Category:** <FR/BR/NFR/SEC/DR/INT/OPS/UX/MT/ADR/TR>",
|
|
23203
|
-
" **Tier:** <platform/industry/customer-workflow/consumer-app>",
|
|
23204
|
-
" **Priority:** <High/Normal/Low>",
|
|
23205
|
-
" **Source:** <document path and section>",
|
|
23206
|
-
"",
|
|
23207
|
-
" ### Summary",
|
|
23208
|
-
" <2-3 sentences describing what the requirement should capture>",
|
|
23209
|
-
"",
|
|
23210
|
-
" ### Draft Acceptance Criteria",
|
|
23211
|
-
" - [ ] <testable criterion 1>",
|
|
23212
|
-
" - [ ] <testable criterion 2>",
|
|
23213
|
-
" - [ ] <testable criterion 3>",
|
|
23214
|
-
"",
|
|
23215
|
-
" ### Traceability",
|
|
23216
|
-
" - **Implements:** <BR or parent requirement if applicable>",
|
|
23217
|
-
" - **Related:** <existing requirements that interact with this one>",
|
|
23218
|
-
" - **Source:** <BCM doc, competitive analysis, or meeting that revealed",
|
|
23219
|
-
" the gap \u2014 use a markdown link. If the source is a meeting note, the",
|
|
23220
|
-
" downstream requirement doc must include the same meeting as a link in",
|
|
23221
|
-
" its Traceability `Related:` list.>",
|
|
23222
|
-
"",
|
|
23223
|
-
" ### Decision Authority",
|
|
23224
|
-
' <"Direct write" for BR/FR/NFR/SEC/UX, or "Proposed \u2014 needs human',
|
|
23225
|
-
' decision" for ADR/TR, or "Mixed \u2014 defer technology choices" for',
|
|
23226
|
-
" DR/MT/INT/OPS>",
|
|
23227
|
-
"",
|
|
23228
|
-
" ### Notes for Requirements Writer",
|
|
23229
|
-
" <any context the writer should know \u2014 related ADRs, existing partial",
|
|
23230
|
-
" coverage, relevant competitive features>",
|
|
23231
|
-
" ```",
|
|
23232
|
-
"",
|
|
23233
|
-
"3. **Write the proposals** to:",
|
|
23234
|
-
" ```",
|
|
23235
|
-
" <RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<YYYY-MM-DD>.md",
|
|
23236
|
-
" ```",
|
|
23237
|
-
"",
|
|
23238
|
-
"4. **Determine next sequence numbers.** Check each target category",
|
|
23239
|
-
" directory under `<REQUIREMENTS_ROOT>/<category>/` to find the next",
|
|
23240
|
-
" available `NNN` for each proposed requirement.",
|
|
23241
|
-
"",
|
|
23242
|
-
"5. **Create requirement issues.** For each proposal, file a",
|
|
23243
|
-
" `req:write` issue using the canonical recipe documented in",
|
|
23244
|
-
" `## Template: req:write` of",
|
|
23245
|
-
" `docs/src/content/docs/agents/issue-templates.md`.",
|
|
23246
|
-
"",
|
|
23247
|
-
` All \`type:requirement\` issues default to \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\` (override`,
|
|
23248
|
-
" only if the proposal's priority was explicitly High or Low). Each",
|
|
23249
|
-
" issue must also carry the `req:write` phase label plus the matching",
|
|
23250
|
-
" `tier:*` label so the downstream `requirements-writer` bundle picks",
|
|
23251
|
-
" it up with the correct tier. Concretely, the label list includes",
|
|
23252
|
-
' `--label "type:requirement"`, `--label "req:write"`,',
|
|
23253
|
-
` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
|
|
23254
|
-
` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
|
|
23255
|
-
"",
|
|
23256
|
-
" The body must carry the three writer-required fields in an",
|
|
23257
|
-
" `## Objective` block, written as bold-prefixed lines so the",
|
|
23258
|
-
" writer's intake parser can pull them out:",
|
|
23259
|
-
"",
|
|
23260
|
-
" - `**Category:** <BR/FR/NFR/TR/ADR/SEC/DR/INT/OPS/UX/MT>`",
|
|
23261
|
-
" - `**Tier:** <platform/industry/customer-workflow/consumer-app>`",
|
|
23262
|
-
" - `**Output Path:** <REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`",
|
|
23263
|
-
"",
|
|
23264
|
-
" Then under `## Inputs / Read`, list the proposals file",
|
|
23265
|
-
" (`<RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<date>.md`)",
|
|
23266
|
-
" and any source documents the writer should consult (BCM model",
|
|
23267
|
-
" docs, competitive analyses, product docs).",
|
|
23268
|
-
"",
|
|
23269
|
-
" When one of Category / Tier / Output Path could not be derived",
|
|
23270
|
-
" from the proposal (for example, the proposal omitted the Tier",
|
|
23271
|
-
` line), replace \`status:${labelsForPhase(issueDefaults, "req:write").status}\` with \`status:needs-attention\` in`,
|
|
23272
|
-
" the label list and add a `Missing: <field> \u2014 <reason>` line",
|
|
23273
|
-
" directly below the Output Path line in the body. Populate",
|
|
23274
|
-
" whichever of the three fields **could** be derived so a human",
|
|
23275
|
-
" triaging the issue has the minimum possible cleanup.",
|
|
23276
|
-
"",
|
|
23277
|
-
"6. **Update source documents.** In each BCM model doc or competitive",
|
|
23278
|
-
" analysis that was scanned, add a note in the project-relevance /",
|
|
23279
|
-
" strategic-implications section (whichever heading the source doc uses)",
|
|
23280
|
-
" indicating that a requirement issue was created:",
|
|
23281
|
-
"",
|
|
23282
|
-
" ```markdown",
|
|
23283
|
-
" - Gap addressed: see [<PREFIX>-<NNN>](<relative path to requirement doc>)",
|
|
23284
|
-
" (issue #<N>)",
|
|
23285
|
-
" ```",
|
|
23286
|
-
"",
|
|
23287
|
-
"7. **Comment on the scan issue** with a summary of all issues created and",
|
|
23288
|
-
" all docs updated.",
|
|
23289
|
-
"",
|
|
23290
|
-
"8. **Commit and push.**",
|
|
23291
|
-
"",
|
|
23292
|
-
"---",
|
|
23293
|
-
"",
|
|
23294
|
-
"## Coordination with Other Agents",
|
|
23295
|
-
"",
|
|
23296
|
-
"| Direction | Agent | What |",
|
|
23297
|
-
"|-----------|-------|------|",
|
|
23298
|
-
`| Upstream | BCM Writer | Scans capability-model docs for project-relevance gaps (judged against \`${paths.docsRoot}/project-context.md\`) |`,
|
|
23299
|
-
"| Upstream | Company Research | Scans competitive analysis for feature comparison gaps |",
|
|
23300
|
-
"| Upstream | Meeting Analyst | Scans meeting extracts for requirement proposals |",
|
|
23301
|
-
"| Downstream | `requirements-writer` | Picks up the `type:requirement` + `req:write` issues this agent creates and drafts the actual requirement document |",
|
|
23302
|
-
"",
|
|
23303
|
-
"**File boundaries:** Writes to `<RESEARCH_REQUIREMENTS_ROOT>/req-*.md` and",
|
|
23304
|
-
"minor traceability edits to `<BCM_DOCS_ROOT>` and `<COMPETITIVE_ROOT>`.",
|
|
23305
|
-
"Never writes to `<REQUIREMENTS_ROOT>/` \u2014 that is owned by the",
|
|
23306
|
-
"requirements-writer agent.",
|
|
23307
|
-
"",
|
|
23308
|
-
"---",
|
|
23309
|
-
"",
|
|
23310
|
-
"## Blocked Issues",
|
|
23311
|
-
"",
|
|
23312
|
-
"Additional block reasons specific to requirements synthesis:",
|
|
23313
|
-
"- Source document has unresolved contradictions",
|
|
23314
|
-
"- Category classification is ambiguous (needs human disambiguation)",
|
|
23315
|
-
"- Dependent BCM documents are still in draft with placeholder content",
|
|
23316
|
-
"",
|
|
23317
|
-
"---",
|
|
23318
|
-
"",
|
|
23319
|
-
"## Rules",
|
|
23320
|
-
"",
|
|
23321
|
-
"- **Discover, don't write requirements.** Create issues for the",
|
|
23322
|
-
" `requirements-writer` agent \u2014 don't write requirement documents",
|
|
23323
|
-
" directly.",
|
|
23324
|
-
"- **Deduplicate rigorously.** Check both existing docs and open issues",
|
|
23325
|
-
" before flagging a gap.",
|
|
23326
|
-
"- **Respect decision authority.** Mark ADR/TR proposals as needing human",
|
|
23327
|
-
" decision. Don't create direct-write issues for technology choices.",
|
|
23328
|
-
"- **Bidirectional traceability.** Every `req-scan-*.md` and",
|
|
23329
|
-
" `req-proposals-*.md` must include a `## Produced` section listing the",
|
|
23330
|
-
" downstream requirement issues (and eventual requirement documents) it",
|
|
23331
|
-
" spawned, as markdown links; each formal requirement document under",
|
|
23332
|
-
" `<REQUIREMENTS_ROOT>/` must include a forward link back to the scan or",
|
|
23333
|
-
" proposal that produced it."
|
|
23334
|
-
].join("\n")
|
|
23335
|
-
};
|
|
23336
|
-
}
|
|
23337
|
-
function buildScanRequirementsSkill(issueDefaults) {
|
|
23338
|
-
return {
|
|
23339
|
-
name: "scan-requirements",
|
|
23340
|
-
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.",
|
|
23341
|
-
disableModelInvocation: true,
|
|
23342
|
-
userInvocable: true,
|
|
23343
|
-
context: "fork",
|
|
23344
|
-
agent: "requirements-analyst",
|
|
23345
|
-
platforms: { cursor: { exclude: true } },
|
|
23346
|
-
instructions: [
|
|
23347
|
-
"# Scan Requirements",
|
|
23348
|
-
"",
|
|
23349
|
-
"Kick off a requirements-analyst scan cycle. Creates a `req:scan` issue",
|
|
23350
|
-
"targeted at the requested scope and dispatches Phase 1 (Scan) in the",
|
|
23351
|
-
"requirements-analyst agent.",
|
|
23352
|
-
"",
|
|
23353
|
-
"## Usage",
|
|
23354
|
-
"",
|
|
23355
|
-
"/scan-requirements <scope>",
|
|
23356
|
-
"",
|
|
23357
|
-
"Where `<scope>` is one of:",
|
|
23358
|
-
"- `bcm:<PREFIX-NNN>` \u2014 a single BCM model doc",
|
|
23359
|
-
"- `competitive:<slug>` \u2014 a single competitive analysis doc",
|
|
23360
|
-
"- `product-roadmap` \u2014 the prioritized feature roadmap",
|
|
23361
|
-
"- `entity-taxonomy` \u2014 the entity taxonomy doc",
|
|
23362
|
-
"- `meeting:<slug>` \u2014 a meeting extract",
|
|
23363
|
-
"- `all-bcm` / `all-competitive` \u2014 full sweep (long-running)",
|
|
23364
|
-
"",
|
|
23365
|
-
"## Steps",
|
|
23366
|
-
"",
|
|
23367
|
-
`1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
|
|
23368
|
-
` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
|
|
23369
|
-
"2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
|
|
23370
|
-
"3. If gaps are found, a `req:draft-trace` issue is created automatically.",
|
|
23371
|
-
"",
|
|
23372
|
-
"## Output",
|
|
23373
|
-
"",
|
|
23374
|
-
"- A `req-scan-<scope>-<YYYY-MM-DD>.md` file under the project's research",
|
|
23375
|
-
" requirements directory.",
|
|
23376
|
-
"- A `req:draft-trace` issue if any gaps were identified."
|
|
23377
|
-
].join("\n")
|
|
23378
|
-
};
|
|
23379
|
-
}
|
|
23380
|
-
function buildRequirementsAnalystBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
|
|
23381
|
-
return {
|
|
23382
|
-
name: "requirements-analyst",
|
|
23383
|
-
description: "Requirements gap-discovery agent bundle for BCM-driven projects. 2-phase pipeline (scan, draft-trace) with req:* phase labels.",
|
|
23384
|
-
appliesWhen: () => true,
|
|
23385
|
-
rules: [
|
|
23386
|
-
{
|
|
23387
|
-
name: "requirements-analyst-workflow",
|
|
23388
|
-
description: "Describes the 2-phase requirements gap-discovery pipeline, the req:* label taxonomy, and the boundary with the downstream requirements-writer agent.",
|
|
23389
|
-
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
23390
|
-
content: [
|
|
23391
|
-
"# Requirements Analyst Workflow",
|
|
23392
|
-
"",
|
|
23393
|
-
"Use `/scan-requirements <scope>` to kick off a requirements gap",
|
|
23394
|
-
"discovery cycle. The pipeline runs in 2 phases \u2014 scan and",
|
|
23395
|
-
"draft-trace \u2014 each tracked by its own GitHub issue labeled",
|
|
23396
|
-
"`req:scan` or `req:draft-trace`. All issues carry",
|
|
23397
|
-
"`type:requirement`.",
|
|
23398
|
-
"",
|
|
23399
|
-
"The requirements-analyst *discovers gaps, drafts proposals, and",
|
|
23400
|
-
"opens `req:write` issues for the downstream writer*; it does",
|
|
23401
|
-
"**not** write final requirement documents. Writing is the job of",
|
|
23402
|
-
"the downstream `requirements-writer` agent (a separate bundle).",
|
|
23403
|
-
"Keep that boundary clean: proposals land under the research",
|
|
23404
|
-
"requirements directory, not under the authoritative requirements",
|
|
23405
|
-
"tree. The draft-trace phase tags new issues with `req:write` so",
|
|
23406
|
-
"the writer bundle picks them up automatically.",
|
|
23407
|
-
"",
|
|
23408
|
-
"See the `requirements-analyst` agent definition for full workflow",
|
|
23409
|
-
"details and phase-by-phase instructions."
|
|
23410
|
-
].join("\n"),
|
|
23411
|
-
platforms: {
|
|
23412
|
-
cursor: { exclude: true }
|
|
23413
|
-
},
|
|
23414
|
-
tags: ["workflow"]
|
|
23415
|
-
}
|
|
23416
|
-
],
|
|
23417
|
-
skills: [buildScanRequirementsSkill(issueDefaults)],
|
|
23418
|
-
subAgents: [buildRequirementsAnalystSubAgent(paths, issueDefaults)],
|
|
23419
|
-
labels: [
|
|
23420
|
-
{
|
|
23421
|
-
name: "type:requirement",
|
|
23422
|
-
color: "1D76DB",
|
|
23423
|
-
description: "Work that produces or discovers a requirement document (FR, BR, NFR, etc.)"
|
|
23424
|
-
},
|
|
23425
|
-
{
|
|
23426
|
-
name: "req:scan",
|
|
23427
|
-
color: "C5DEF5",
|
|
23428
|
-
description: "Phase 1: scan source docs for requirement gaps and deduplicate"
|
|
23429
|
-
},
|
|
23430
|
-
{
|
|
23431
|
-
name: "req:draft-trace",
|
|
23432
|
-
color: "BFDADC",
|
|
23433
|
-
description: "Phase 2: draft proposals, create req:write issues, and backfill source-doc traceability"
|
|
23434
|
-
}
|
|
23435
|
-
]
|
|
23436
|
-
};
|
|
23437
|
-
}
|
|
23438
|
-
var requirementsAnalystBundle = buildRequirementsAnalystBundle();
|
|
23439
|
-
|
|
23440
23140
|
// src/agent/bundles/requirements-writer.ts
|
|
23441
23141
|
var templateBr = (paths) => `---
|
|
23442
23142
|
title: "BR-NNN: [Business Requirement Title]"
|
|
@@ -24797,7 +24497,7 @@ Use [\`_template-{PREFIX}.md\`](../templates/_template-{PREFIX}.md) for all new
|
|
|
24797
24497
|
## Conventions
|
|
24798
24498
|
|
|
24799
24499
|
- File naming: \`{PREFIX}-NNN-descriptive-slug.md\`
|
|
24800
|
-
- Number sequentially \u2014 check the table above for the next available number
|
|
24500
|
+
- 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.
|
|
24801
24501
|
- Every document must include YAML frontmatter with a \`title\` field
|
|
24802
24502
|
- Every document must include a \`## Traceability\` section
|
|
24803
24503
|
`;
|
|
@@ -25573,9 +25273,14 @@ function buildRequirementsWriterSubAgent(paths) {
|
|
|
25573
25273
|
" `ADR`, `SEC`, `DR`, `INT`, `OPS`, `UX`, `MT`). If the project",
|
|
25574
25274
|
` declares a project-wide prefix in \`${paths.docsRoot}/project-context.md\`, use`,
|
|
25575
25275
|
" it instead.",
|
|
25576
|
-
"- **NNN** \u2014
|
|
25577
|
-
"
|
|
25578
|
-
"
|
|
25276
|
+
"- **NNN** \u2014 sequential number, zero-padded to at least three",
|
|
25277
|
+
" digits (001, 002, 012; grows past 999 naturally, e.g. 1240). The",
|
|
25278
|
+
" ID is **finalized at write-start** via",
|
|
25279
|
+
" `.claude/procedures/next-requirement-id.sh`, which allocates from",
|
|
25280
|
+
" live state (existing docs plus open `req:write` issues). Scanning",
|
|
25281
|
+
" the target category directory under `<REQUIREMENTS_ROOT>` for the",
|
|
25282
|
+
" next available number is the **fallback** used only when that",
|
|
25283
|
+
" procedure is unavailable.",
|
|
25579
25284
|
"- **slug** \u2014 lowercase kebab-case descriptive name.",
|
|
25580
25285
|
"",
|
|
25581
25286
|
"Examples: `FR-001-user-registration.md`,",
|
|
@@ -25760,153 +25465,939 @@ function buildRequirementsWriterSubAgent(paths) {
|
|
|
25760
25465
|
" PR workflow. Closing the PR transitions the issue to",
|
|
25761
25466
|
" `status:done` per the standard label conventions.",
|
|
25762
25467
|
"",
|
|
25763
|
-
"---",
|
|
25468
|
+
"---",
|
|
25469
|
+
"",
|
|
25470
|
+
"## The `req:write` Issue Schema",
|
|
25471
|
+
"",
|
|
25472
|
+
"This section is the **authoritative schema** for `req:write` issue",
|
|
25473
|
+
"bodies. Upstream producers (the `requirements-analyst` trace phase,",
|
|
25474
|
+
"the `requirements-reviewer` follow-up generator, and any human",
|
|
25475
|
+
"authoring a `req:write` issue directly) must embed or link to this",
|
|
25476
|
+
"same schema so the three required fields are always present when",
|
|
25477
|
+
"the writer picks the issue up.",
|
|
25478
|
+
"",
|
|
25479
|
+
...REQ_WRITE_ISSUE_SCHEMA_SECTION,
|
|
25480
|
+
"",
|
|
25481
|
+
"---",
|
|
25482
|
+
"",
|
|
25483
|
+
"## The `req:write` Phase",
|
|
25484
|
+
"",
|
|
25485
|
+
"**Goal:** Read the proposal that produced this issue, write a single",
|
|
25486
|
+
"requirement document under `<REQUIREMENTS_ROOT>`, and update the",
|
|
25487
|
+
"category index.",
|
|
25488
|
+
"",
|
|
25489
|
+
"**Budget:** Read the issue body, the named proposal file under",
|
|
25490
|
+
"`<RESEARCH_REQUIREMENTS_ROOT>`, the matching category template under",
|
|
25491
|
+
"`<TEMPLATES_ROOT>`, and `<STANDARDS_REF>` if the category needs it.",
|
|
25492
|
+
"Write one requirement document and one category-index update. No web",
|
|
25493
|
+
"searches.",
|
|
25494
|
+
"",
|
|
25495
|
+
"### Steps",
|
|
25496
|
+
"",
|
|
25497
|
+
"1. **Parse the issue.** The `req:write` issue body follows the",
|
|
25498
|
+
" authoritative schema in the",
|
|
25499
|
+
" [The `req:write` Issue Schema](#the-reqwrite-issue-schema)",
|
|
25500
|
+
" section below. Before writing, verify every required field",
|
|
25501
|
+
" (Category, Tier, Output Path) is present and internally",
|
|
25502
|
+
" consistent:",
|
|
25503
|
+
"",
|
|
25504
|
+
" - Category matches the prefix on the requirement title and on",
|
|
25505
|
+
" the Output Path filename.",
|
|
25506
|
+
" - Tier matches the `tier:*` label applied to the issue.",
|
|
25507
|
+
" - Output Path sits under",
|
|
25508
|
+
" `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`.",
|
|
25509
|
+
"",
|
|
25510
|
+
" If any required field is missing, contradictory, or the issue",
|
|
25511
|
+
" already carries `status:needs-attention` with a `Missing:` line",
|
|
25512
|
+
" (upstream could not derive it), comment on the issue with",
|
|
25513
|
+
" which field is wrong, ensure `status:needs-attention` is set,",
|
|
25514
|
+
" and stop without writing. A human triages before the writer",
|
|
25515
|
+
" tries again.",
|
|
25516
|
+
"",
|
|
25517
|
+
"2. **Read the proposal.** Open the proposals file referenced in the",
|
|
25518
|
+
" issue inputs (under `<RESEARCH_REQUIREMENTS_ROOT>`). Find the",
|
|
25519
|
+
" specific proposal entry that matches this requirement's title and",
|
|
25520
|
+
" category. Treat the proposal as the authoritative source \u2014 do not",
|
|
25521
|
+
" invent fields it omits.",
|
|
25522
|
+
"",
|
|
25523
|
+
"3. **Read the matching template** under `<TEMPLATES_ROOT>` for the",
|
|
25524
|
+
" category. Read `<STANDARDS_REF>` if the category is SEC, NFR,",
|
|
25525
|
+
" INT, ADR, or TR \u2014 these depend on the standards reference for",
|
|
25526
|
+
" correct framing.",
|
|
25527
|
+
"",
|
|
25528
|
+
"4. **Finalize the requirement ID first \u2014 before you author any",
|
|
25529
|
+
" document content, filename, or self-reference.** Run",
|
|
25530
|
+
" `.claude/procedures/next-requirement-id.sh <PREFIX> <category-dir> <this-issue-number>`",
|
|
25531
|
+
" to compute the next free ID from live state (existing docs plus",
|
|
25532
|
+
" open `req:write` issues, with the issue-number tiebreak). Use",
|
|
25533
|
+
" `<category-dir>` = the resolved `<REQUIREMENTS_ROOT>/<category-dir>`",
|
|
25534
|
+
" for the target category, and `<this-issue-number>` = the number of",
|
|
25535
|
+
" the `req:write` issue you are working.",
|
|
25536
|
+
"",
|
|
25537
|
+
" - If the issue's reserved `Requirement ID` hint is present **and",
|
|
25538
|
+
" equals** the procedure's output, use it.",
|
|
25539
|
+
" - Otherwise use the **procedure's output** \u2014 a concurrent claim",
|
|
25540
|
+
" took the reserved number, and the procedure has already resolved",
|
|
25541
|
+
" the collision in your favor via the issue-number order.",
|
|
25542
|
+
"",
|
|
25543
|
+
" **Author the document exactly once with this finalized ID** \u2014 the",
|
|
25544
|
+
" filename and every in-body self-reference. **Never renumber after",
|
|
25545
|
+
" authoring:** the ID is fixed before any body content exists, so a",
|
|
25546
|
+
" concurrent claim is resolved here for free \u2014 no rename, no",
|
|
25547
|
+
" self-reference rewrite, no post-authoring renumber treadmill.",
|
|
25548
|
+
"",
|
|
25549
|
+
" After finalizing, and before opening the PR, you SHOULD update this",
|
|
25550
|
+
" issue's `Requirement ID` field to the finalized value",
|
|
25551
|
+
" (`gh issue edit <this-issue-number>`) so other in-flight writers",
|
|
25552
|
+
" see the resolved claim. This is best-effort \u2014 do not block or fail",
|
|
25553
|
+
" the write if the edit does not succeed.",
|
|
25554
|
+
"",
|
|
25555
|
+
" If `next-requirement-id.sh` is unavailable, fall back to scanning",
|
|
25556
|
+
" the target category directory under `<REQUIREMENTS_ROOT>` for",
|
|
25557
|
+
" existing files matching `{PREFIX}-NNN-*.md` and take the next",
|
|
25558
|
+
" unused three-digit number.",
|
|
25559
|
+
"",
|
|
25560
|
+
"5. **Apply the decision-authority rules.** Default to `Status:",
|
|
25561
|
+
" Draft` for direct-write categories (BR, FR, NFR, SEC, UX). Set",
|
|
25562
|
+
" `Status: Proposed` for ADR and TR documents and frame the",
|
|
25563
|
+
" Recommendation section as a human decision. For DR/MT/INT/OPS",
|
|
25564
|
+
" categories that imply a technology choice, write the requirement",
|
|
25565
|
+
" directly but spin the technology decision off into a separate",
|
|
25566
|
+
" `Proposed` ADR or TR \u2014 record the cross-link in the Open Items",
|
|
25567
|
+
" section of both documents.",
|
|
25568
|
+
"",
|
|
25569
|
+
"6. **Write the document.** Fill every section in the template. For",
|
|
25570
|
+
" sections the proposal does not supply, write `TODO:` plus a brief",
|
|
25571
|
+
" note describing what input is needed, and add a corresponding",
|
|
25572
|
+
" `## Open Items` entry. Never leave a template section out.",
|
|
25573
|
+
"",
|
|
25574
|
+
"7. **Set frontmatter.** At minimum: `title`, `description`, `tier`.",
|
|
25575
|
+
" If the project declares optional frontmatter conventions in",
|
|
25576
|
+
` \`${paths.docsRoot}/project-context.md\` (such as \`referencedIn.meetings[]\`),`,
|
|
25577
|
+
" honor them. Otherwise stop at the minimum.",
|
|
25578
|
+
"",
|
|
25579
|
+
"8. **Update the category index.** If the category directory has a",
|
|
25580
|
+
" `README.md` or `_index.md` registry, add a row for the new",
|
|
25581
|
+
" document in sequence order. If no index exists yet, generate one",
|
|
25582
|
+
" from `_template-category-README.md`. The requirement ID was",
|
|
25583
|
+
" already finalized at write-start (step 4), so this is a plain",
|
|
25584
|
+
" append-only, deterministic row insert \u2014 never renumber existing",
|
|
25585
|
+
" rows or mutate a sequence counter here.",
|
|
25586
|
+
"",
|
|
25587
|
+
"9. **Cross-link upstream.** Add `## Traceability` entries pointing",
|
|
25588
|
+
" to the proposals file (under `<RESEARCH_REQUIREMENTS_ROOT>`),",
|
|
25589
|
+
" the source documents the proposal cited, and any BCM capability",
|
|
25590
|
+
" the requirement supports.",
|
|
25591
|
+
"",
|
|
25592
|
+
"10. **Quality checks.** Before committing, verify:",
|
|
25593
|
+
" - [ ] Frontmatter has `title` and `description` (and `tier` if",
|
|
25594
|
+
" the project uses tier classification)",
|
|
25595
|
+
" - [ ] Title matches the `# Heading` line",
|
|
25596
|
+
" - [ ] File name follows `{PREFIX}-{NNN}-{slug}.md`",
|
|
25597
|
+
" - [ ] Status is `Draft` for direct-write categories or `Proposed`",
|
|
25598
|
+
" for ADR/TR",
|
|
25599
|
+
" - [ ] Every template section is present (use `TODO:` or `Not",
|
|
25600
|
+
" applicable \u2014 <reason>` for unfilled sections)",
|
|
25601
|
+
" - [ ] `## Traceability` exists with at least one upstream link",
|
|
25602
|
+
" - [ ] No technology decisions made in direct-write categories \u2014",
|
|
25603
|
+
" deferred to `Proposed` ADR/TR with cross-links in Open",
|
|
25604
|
+
" Items",
|
|
25605
|
+
" - [ ] `## Open Items` is present with Identified Gaps,",
|
|
25606
|
+
" Follow-up Questions, and Contradictions subsections",
|
|
25607
|
+
" - [ ] ADR/TR documents include a Recommendation section and an",
|
|
25608
|
+
" Open Item flagging the human decision required",
|
|
25609
|
+
" - [ ] Category index updated with a row for the new document",
|
|
25610
|
+
" - [ ] Tier value matches the issue's `tier:*` label",
|
|
25611
|
+
" - [ ] Cross-tier traceability entries exist where the document",
|
|
25612
|
+
" depends on or enables a different tier",
|
|
25613
|
+
"",
|
|
25614
|
+
"11. **Commit and push.** Use a `docs(<category>):` conventional",
|
|
25615
|
+
" commit message. The PR closes the `req:write` issue.",
|
|
25616
|
+
"",
|
|
25617
|
+
"---",
|
|
25618
|
+
"",
|
|
25619
|
+
"## Output Boundaries",
|
|
25620
|
+
"",
|
|
25621
|
+
"This agent writes **only** to:",
|
|
25622
|
+
"",
|
|
25623
|
+
"- `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md` \u2014 one",
|
|
25624
|
+
" requirement document per session",
|
|
25625
|
+
"- `<REQUIREMENTS_ROOT>/<category-dir>/README.md` (or `_index.md`) \u2014",
|
|
25626
|
+
" category index, one row appended per session",
|
|
25627
|
+
"- `<REQUIREMENTS_ROOT>/README.md` (or `_index.md`) \u2014 only if the",
|
|
25628
|
+
" top-level requirements README does not yet exist; generate from",
|
|
25629
|
+
" `_template-requirements-README.md` and stop",
|
|
25630
|
+
"",
|
|
25631
|
+
"The pipeline produces **requirement documents**. It does not write",
|
|
25632
|
+
"BCM capability models, people profiles, company profiles, software",
|
|
25633
|
+
"profiles, scan reports, or proposal files \u2014 those belong to",
|
|
25634
|
+
"specialized upstream/downstream agents.",
|
|
25635
|
+
"",
|
|
25636
|
+
"**Do NOT create:**",
|
|
25637
|
+
"- `req:scan` or `req:draft-trace` issues \u2014 those belong to",
|
|
25638
|
+
" the `requirements-analyst` bundle",
|
|
25639
|
+
"- `bcm:*` issues \u2014 those belong to the `bcm-writer` bundle",
|
|
25640
|
+
"- `people:*`, `company:*`, `software:*`, `research:*`, or",
|
|
25641
|
+
" `industry:*` issues \u2014 those belong to their respective bundles",
|
|
25642
|
+
"",
|
|
25643
|
+
"If the proposal surfaces work that needs one of the above, comment",
|
|
25644
|
+
"on the `req:write` issue with the suggested follow-up and let a",
|
|
25645
|
+
"human route it.",
|
|
25646
|
+
"",
|
|
25647
|
+
"---",
|
|
25648
|
+
"",
|
|
25649
|
+
"## Coordination with Other Agents",
|
|
25650
|
+
"",
|
|
25651
|
+
"| Direction | Agent | What |",
|
|
25652
|
+
"|-----------|-------|------|",
|
|
25653
|
+
"| Upstream | `requirements-analyst` | Discovers gaps, drafts proposals, and creates `req:write` issues that this agent picks up |",
|
|
25654
|
+
"| Upstream | `bcm-writer` | Provides BCM capability documents that requirements trace back to via `## Traceability` links |",
|
|
25655
|
+
"| Peer | `meeting-analyst` | Provides meeting transcripts that may inform a requirement's traceability extensions (optional) |",
|
|
25656
|
+
"",
|
|
25657
|
+
"**File boundaries:** Reads `<RESEARCH_REQUIREMENTS_ROOT>` (proposals)",
|
|
25658
|
+
"and the source documents the proposals cite. Writes",
|
|
25659
|
+
"`<REQUIREMENTS_ROOT>` and the category index files. Never edits",
|
|
25660
|
+
"proposals, scan reports, BCM documents, or profiles.",
|
|
25661
|
+
"",
|
|
25662
|
+
"---",
|
|
25663
|
+
"",
|
|
25664
|
+
"## Rules",
|
|
25665
|
+
"",
|
|
25666
|
+
"- **One requirement per session.** Never write two documents in one",
|
|
25667
|
+
" session and never start a second issue.",
|
|
25668
|
+
"- **Templates are authoritative.** Use the shipped template verbatim",
|
|
25669
|
+
" for the category. Every template section must appear in the final",
|
|
25670
|
+
" document.",
|
|
25671
|
+
"- **Decision authority is non-negotiable.** Direct-write categories",
|
|
25672
|
+
" ship as `Draft`. ADR and TR ship as `Proposed` with a Recommendation",
|
|
25673
|
+
" framed for human decision. Mixed-deferral categories spin",
|
|
25674
|
+
" technology choices off into separate `Proposed` documents.",
|
|
25675
|
+
"- **Cite, don't invent.** When the proposal omits a stakeholder,",
|
|
25676
|
+
" metric, threat model entry, or technology option, write `TODO:` and",
|
|
25677
|
+
" flag the issue with `status:needs-attention`.",
|
|
25678
|
+
"- **Trace upstream.** Every requirement links back to its proposal,",
|
|
25679
|
+
" the source documents the proposal cited, and the BCM capability",
|
|
25680
|
+
" it supports (when applicable).",
|
|
25681
|
+
"- **Update the category index every time.** A requirement that",
|
|
25682
|
+
" exists as a file but is missing from its category index is",
|
|
25683
|
+
" invisible to anyone browsing the documentation tree.",
|
|
25684
|
+
"- **Write requirements, not capability models or gap reports.**",
|
|
25685
|
+
" Never open `req:scan`, `req:draft-trace`, or `bcm:*` issues from",
|
|
25686
|
+
" this pipeline."
|
|
25687
|
+
].join("\n")
|
|
25688
|
+
};
|
|
25689
|
+
}
|
|
25690
|
+
function buildWriteRequirementSkill(paths, issueDefaults) {
|
|
25691
|
+
return {
|
|
25692
|
+
name: WRITE_REQUIREMENT_SKILL_NAME,
|
|
25693
|
+
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.",
|
|
25694
|
+
disableModelInvocation: true,
|
|
25695
|
+
userInvocable: true,
|
|
25696
|
+
context: "fork",
|
|
25697
|
+
agent: "requirements-writer",
|
|
25698
|
+
platforms: { cursor: { exclude: true } },
|
|
25699
|
+
referenceFiles: buildRequirementsWriterReferenceFiles(paths),
|
|
25700
|
+
instructions: [
|
|
25701
|
+
"# Write Requirement",
|
|
25702
|
+
"",
|
|
25703
|
+
"Write one formal requirement document using the 11-category taxonomy",
|
|
25704
|
+
"(BR, FR, NFR, TR, ADR, SEC, DR, INT, OPS, UX, MT) and the",
|
|
25705
|
+
"decision-authority rules (direct-write vs. propose-only ADR/TR).",
|
|
25706
|
+
"Dispatches the `requirements-writer` agent.",
|
|
25707
|
+
"",
|
|
25708
|
+
"## Usage",
|
|
25709
|
+
"",
|
|
25710
|
+
"/write-requirement <category> <short-title>",
|
|
25711
|
+
"",
|
|
25712
|
+
"Where `<category>` is one of `BR`, `FR`, `NFR`, `TR`, `ADR`, `SEC`,",
|
|
25713
|
+
"`DR`, `INT`, `OPS`, `UX`, `MT`.",
|
|
25714
|
+
"",
|
|
25715
|
+
"Optional extensions in the issue body:",
|
|
25716
|
+
"- `tier: platform | industry | customer-workflow | consumer-app` \u2014",
|
|
25717
|
+
" the architectural tier (default: `platform`)",
|
|
25718
|
+
"- `prefix: <PROJECT_PREFIX>` \u2014 override the default category prefix",
|
|
25719
|
+
` with a project-specific one declared in \`${paths.docsRoot}/project-context.md\``,
|
|
25720
|
+
"- `customer: <link-or-slug>` \u2014 link the requirement to a customer",
|
|
25721
|
+
" profile (expected for Customer Workflow / Consumer Application",
|
|
25722
|
+
" tiers in projects that track customer profiles)",
|
|
25723
|
+
"- `proposal: <path>` \u2014 pin the upstream proposal file under",
|
|
25724
|
+
" `<RESEARCH_REQUIREMENTS_ROOT>` (default: derived from the issue",
|
|
25725
|
+
" context)",
|
|
25726
|
+
"- `output: <path>` \u2014 override the default Output Path",
|
|
25727
|
+
"",
|
|
25728
|
+
"## Default Paths",
|
|
25729
|
+
"",
|
|
25730
|
+
`If the project has no override in \`${paths.docsRoot}/project-context.md\` or`,
|
|
25731
|
+
"`agentConfig.rules`, outputs land under:",
|
|
25732
|
+
"",
|
|
25733
|
+
`- \`${paths.requirementsRoot}/<category-dir>/<PREFIX>-<NNN>-<slug>.md\``,
|
|
25734
|
+
`- \`${paths.requirementsRoot}/<category-dir>/README.md\` (registry update)`,
|
|
25735
|
+
`- \`${paths.requirementsRoot}/README.md\` (top-level README, generated on`,
|
|
25736
|
+
" first use only)",
|
|
25737
|
+
"",
|
|
25738
|
+
"Templates and the standards reference ship with this skill under",
|
|
25739
|
+
"`_references/templates/` and `_references/standards-and-frameworks.md`.",
|
|
25740
|
+
"",
|
|
25741
|
+
"## Steps",
|
|
25742
|
+
"",
|
|
25743
|
+
"1. Create a `req:write` issue with `type:requirement`,",
|
|
25744
|
+
` \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\`, \`status:${labelsForPhase(issueDefaults, "req:write").status}\`, and the matching \`tier:*\` label.`,
|
|
25745
|
+
" Body must include the category, tier, output path, and a pointer",
|
|
25746
|
+
" to the upstream proposal (or a direct user description if no",
|
|
25747
|
+
" proposals file exists).",
|
|
25748
|
+
"2. Execute the write phase of the requirements-writer agent.",
|
|
25749
|
+
"3. The agent writes one requirement document, updates the category",
|
|
25750
|
+
" index, opens a PR, and closes the issue.",
|
|
25751
|
+
"",
|
|
25752
|
+
"## Output",
|
|
25753
|
+
"",
|
|
25754
|
+
"- One requirement document under `<REQUIREMENTS_ROOT>` following the",
|
|
25755
|
+
" shipped category template, with `Status: Draft` for direct-write",
|
|
25756
|
+
" categories or `Status: Proposed` for ADR/TR",
|
|
25757
|
+
"- A category-index row pointing at the new document",
|
|
25758
|
+
"- (First-time only) a top-level requirements README derived from",
|
|
25759
|
+
" `_template-requirements-README.md`"
|
|
25760
|
+
].join("\n")
|
|
25761
|
+
};
|
|
25762
|
+
}
|
|
25763
|
+
function renderNextRequirementIdProcedure() {
|
|
25764
|
+
return [
|
|
25765
|
+
"#!/usr/bin/env bash",
|
|
25766
|
+
"# next-requirement-id.sh \u2014 print the next free requirement ID for a",
|
|
25767
|
+
"# category, allocated from live GitHub state (open req:write issues)",
|
|
25768
|
+
"# plus the merged docs already on disk.",
|
|
25769
|
+
"#",
|
|
25770
|
+
"# Usage:",
|
|
25771
|
+
"# .claude/procedures/next-requirement-id.sh <PREFIX> <category-dir> [<self-issue-number>]",
|
|
25772
|
+
"#",
|
|
25773
|
+
"# Example:",
|
|
25774
|
+
"# next-requirement-id.sh FR docs/src/content/docs/requirements/functional 850",
|
|
25775
|
+
"# -> FR-1240",
|
|
25776
|
+
"#",
|
|
25777
|
+
"# Behaviour:",
|
|
25778
|
+
"# - Collects candidate NNN numbers from (1) files in <category-dir>",
|
|
25779
|
+
"# matching ^<PREFIX>-([0-9]+)-.*\\.md$ and (2) open req:write",
|
|
25780
|
+
"# issues whose body carries a `Requirement ID: <PREFIX>-<NNN>`",
|
|
25781
|
+
"# line. Prints `<PREFIX>-<max+1>` zero-padded to at least three",
|
|
25782
|
+
"# digits (grows past 999 naturally, e.g. FR-1240).",
|
|
25783
|
+
"# - When <self-issue-number> is given, only open req:write issues",
|
|
25784
|
+
"# with a STRICTLY LOWER number are counted. This excludes the",
|
|
25785
|
+
"# caller's own reservation and imposes an issue-number total",
|
|
25786
|
+
"# order so two concurrent writers never both pick the same",
|
|
25787
|
+
"# number. Omit it (analyst use, before any issue is filed) to",
|
|
25788
|
+
"# count every open req:write issue.",
|
|
25789
|
+
"#",
|
|
25790
|
+
"# Guards:",
|
|
25791
|
+
"# - Side-effect-free: never edits issues, docs, or any counter.",
|
|
25792
|
+
"# - Every gh/jq call is guarded (|| true, 2>/dev/null); a missing",
|
|
25793
|
+
"# binary, missing auth, or non-repo cwd contributes nothing and",
|
|
25794
|
+
"# the helper falls back to the directory scan.",
|
|
25795
|
+
"# - Prints a usage message to stderr and exits 2 on bad args; a",
|
|
25796
|
+
"# missing or empty <category-dir> is tolerated (scan finds",
|
|
25797
|
+
"# nothing). Happy path always exits 0.",
|
|
25798
|
+
"",
|
|
25799
|
+
"set -uo pipefail",
|
|
25800
|
+
"",
|
|
25801
|
+
"usage() {",
|
|
25802
|
+
' printf "usage: next-requirement-id.sh <PREFIX> <category-dir> [<self-issue-number>]\\n" >&2',
|
|
25803
|
+
"}",
|
|
25804
|
+
"",
|
|
25805
|
+
"# Require at least a prefix and a category directory argument.",
|
|
25806
|
+
'if [ "$#" -lt 2 ]; then',
|
|
25807
|
+
" usage",
|
|
25808
|
+
" exit 2",
|
|
25809
|
+
"fi",
|
|
25810
|
+
"",
|
|
25811
|
+
'prefix="$1"',
|
|
25812
|
+
'category_dir="$2"',
|
|
25813
|
+
'self_issue="${3:-}"',
|
|
25814
|
+
"",
|
|
25815
|
+
"# The running maximum NNN seen across both candidate sources. Starts",
|
|
25816
|
+
"# at 0 so an empty category with no open issues yields <PREFIX>-001.",
|
|
25817
|
+
"max=0",
|
|
25818
|
+
"",
|
|
25819
|
+
"# Fold a bare integer candidate into the running max. Ignores any",
|
|
25820
|
+
"# value that is not a run of digits (defensive against malformed",
|
|
25821
|
+
"# filenames or issue bodies).",
|
|
25822
|
+
"consider() {",
|
|
25823
|
+
' local n="$1"',
|
|
25824
|
+
' case "$n" in',
|
|
25825
|
+
" '' | *[!0-9]*) return 0 ;;",
|
|
25826
|
+
" esac",
|
|
25827
|
+
" # Strip leading zeros for base-10 arithmetic (avoid octal parsing).",
|
|
25828
|
+
" n=$((10#$n))",
|
|
25829
|
+
' if [ "$n" -gt "$max" ]; then',
|
|
25830
|
+
' max="$n"',
|
|
25831
|
+
" fi",
|
|
25832
|
+
"}",
|
|
25833
|
+
"",
|
|
25834
|
+
"# --- Source 1: merged docs on disk -------------------------------------",
|
|
25835
|
+
"# Scan <category-dir> for files named <PREFIX>-<NNN>-<slug>.md and fold",
|
|
25836
|
+
"# each NNN into the running max. A missing/empty directory is a no-op.",
|
|
25837
|
+
'if [ -n "$category_dir" ] && [ -d "$category_dir" ]; then',
|
|
25838
|
+
' for f in "$category_dir"/"$prefix"-*.md; do',
|
|
25839
|
+
" # Guard the literal-glob case when nothing matches.",
|
|
25840
|
+
' [ -e "$f" ] || continue',
|
|
25841
|
+
' base="$(basename "$f")"',
|
|
25842
|
+
" # Extract the digit run between the prefix and the next hyphen.",
|
|
25843
|
+
' nnn="${base#"$prefix"-}"',
|
|
25844
|
+
' nnn="${nnn%%-*}"',
|
|
25845
|
+
' consider "$nnn"',
|
|
25846
|
+
" done",
|
|
25847
|
+
"fi",
|
|
25848
|
+
"",
|
|
25849
|
+
"# --- Source 2: live state \u2014 open req:write issues ----------------------",
|
|
25850
|
+
"# Ask GitHub for open req:write issues and parse each body for a",
|
|
25851
|
+
"# `Requirement ID: <PREFIX>-<NNN>` field. The whole block is guarded so",
|
|
25852
|
+
"# a missing gh/jq, missing auth, or non-repo cwd simply yields no",
|
|
25853
|
+
"# candidates and the directory scan stands alone.",
|
|
25854
|
+
"if command -v gh >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then",
|
|
25855
|
+
' issues_json="$(gh issue list --label req:write --state open --limit 500 --json number,body 2>/dev/null || true)"',
|
|
25856
|
+
' if [ -n "$issues_json" ]; then',
|
|
25857
|
+
" # Flatten each issue to a single `<number>\\t<body>` line (newlines",
|
|
25858
|
+
" # in the body collapsed to spaces) so a plain read loop can walk",
|
|
25859
|
+
" # them. jq is guarded; on any parse error `parsed` is empty and the",
|
|
25860
|
+
" # loop runs zero times. Captured into a variable first so the loop",
|
|
25861
|
+
" # runs in THIS shell (a piped `while` would subshell and lose max).",
|
|
25862
|
+
` parsed="$(printf '%s' "$issues_json" | jq -r '.[] | "\\(.number)\\t\\(.body // "" | gsub("[\\\\n\\\\r]"; " "))"' 2>/dev/null || true)"`,
|
|
25863
|
+
' if [ -n "$parsed" ]; then',
|
|
25864
|
+
" while IFS=$'\\t' read -r issue_number issue_body; do",
|
|
25865
|
+
' [ -n "$issue_number" ] || continue',
|
|
25866
|
+
" # Skip non-numeric issue numbers defensively.",
|
|
25867
|
+
' case "$issue_number" in',
|
|
25868
|
+
" '' | *[!0-9]*) continue ;;",
|
|
25869
|
+
" esac",
|
|
25870
|
+
" # Issue-number total order: when a self issue is given, count",
|
|
25871
|
+
" # this issue only if it is STRICTLY lower-numbered than us.",
|
|
25872
|
+
' if [ -n "$self_issue" ]; then',
|
|
25873
|
+
' case "$self_issue" in',
|
|
25874
|
+
" '' | *[!0-9]*) : ;;",
|
|
25875
|
+
" *)",
|
|
25876
|
+
' if [ "$issue_number" -ge "$self_issue" ]; then',
|
|
25877
|
+
" continue",
|
|
25878
|
+
" fi",
|
|
25879
|
+
" ;;",
|
|
25880
|
+
" esac",
|
|
25881
|
+
" fi",
|
|
25882
|
+
" # Pull the NNN out of a `Requirement ID: <PREFIX>-<NNN>` field.",
|
|
25883
|
+
" # Tolerant of surrounding markdown (**bold**, leading `- `) and",
|
|
25884
|
+
" # case-insensitive on the label. First match wins.",
|
|
25885
|
+
` id_match="$(printf '%s\\n' "$issue_body" | grep -ioE "Requirement ID:[^A-Za-z0-9]*$prefix-[0-9]+" | head -n1 || true)"`,
|
|
25886
|
+
' if [ -n "$id_match" ]; then',
|
|
25887
|
+
` id_nnn="$(printf '%s' "$id_match" | grep -oE '[0-9]+$' || true)"`,
|
|
25888
|
+
' consider "$id_nnn"',
|
|
25889
|
+
" fi",
|
|
25890
|
+
" done <<EOF",
|
|
25891
|
+
"$parsed",
|
|
25892
|
+
"EOF",
|
|
25893
|
+
" fi",
|
|
25894
|
+
" fi",
|
|
25895
|
+
"fi",
|
|
25896
|
+
"",
|
|
25897
|
+
"# next = max seen + 1, printed zero-padded to at least three digits.",
|
|
25898
|
+
"next=$((max + 1))",
|
|
25899
|
+
`printf '%s-%03d\\n' "$prefix" "$next"`,
|
|
25900
|
+
"",
|
|
25901
|
+
"exit 0"
|
|
25902
|
+
].join("\n");
|
|
25903
|
+
}
|
|
25904
|
+
var nextRequirementIdProcedure = {
|
|
25905
|
+
name: "next-requirement-id.sh",
|
|
25906
|
+
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.",
|
|
25907
|
+
content: renderNextRequirementIdProcedure()
|
|
25908
|
+
};
|
|
25909
|
+
function buildRequirementsWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
|
|
25910
|
+
return {
|
|
25911
|
+
name: "requirements-writer",
|
|
25912
|
+
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.",
|
|
25913
|
+
appliesWhen: () => true,
|
|
25914
|
+
rules: [
|
|
25915
|
+
{
|
|
25916
|
+
name: "requirements-writer-workflow",
|
|
25917
|
+
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.",
|
|
25918
|
+
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
25919
|
+
content: [
|
|
25920
|
+
"# Requirements Writer Workflow",
|
|
25921
|
+
"",
|
|
25922
|
+
"Use `/write-requirement <category> <short-title>` to author one",
|
|
25923
|
+
"formal requirement document. The writer runs in a single phase",
|
|
25924
|
+
"tracked by a GitHub issue labeled `req:write` plus the matching",
|
|
25925
|
+
"`tier:*` label. Issues also carry `type:requirement` (declared",
|
|
25926
|
+
"by the upstream `requirements-analyst` bundle).",
|
|
25927
|
+
"",
|
|
25928
|
+
"The pipeline produces **requirement documents only** \u2014 capability",
|
|
25929
|
+
"models are written by the `bcm-writer` agent and gap discovery is",
|
|
25930
|
+
"the responsibility of the `requirements-analyst` agent. The",
|
|
25931
|
+
"writer never opens `req:scan`, `req:draft-trace`, or `bcm:*`",
|
|
25932
|
+
"issues.",
|
|
25933
|
+
"",
|
|
25934
|
+
"Documents follow the 11-category taxonomy (BR, FR, NFR, TR, ADR,",
|
|
25935
|
+
"SEC, DR, INT, OPS, UX, MT) and the four-tier classification",
|
|
25936
|
+
"(Platform, Industry, Customer Workflow, Consumer Application).",
|
|
25937
|
+
"Templates and a standards-and-frameworks reference ship with the",
|
|
25938
|
+
"skill \u2014 they are the same files for every project that adopts",
|
|
25939
|
+
"the bundle.",
|
|
25940
|
+
"",
|
|
25941
|
+
"Decision-authority rules are non-negotiable: BR / FR / NFR /",
|
|
25942
|
+
"SEC / UX ship as `Status: Draft`; ADR and TR ship as",
|
|
25943
|
+
"`Status: Proposed` with a Recommendation framed for human",
|
|
25944
|
+
"decision; DR / MT / INT / OPS spin technology choices off into",
|
|
25945
|
+
"separate `Proposed` ADR or TR documents.",
|
|
25946
|
+
"",
|
|
25947
|
+
"See the `requirements-writer` agent definition for full workflow",
|
|
25948
|
+
"details, configurable paths, decision-authority rules, and",
|
|
25949
|
+
"phase-by-phase instructions."
|
|
25950
|
+
].join("\n"),
|
|
25951
|
+
platforms: {
|
|
25952
|
+
cursor: { exclude: true }
|
|
25953
|
+
},
|
|
25954
|
+
tags: ["workflow"]
|
|
25955
|
+
}
|
|
25956
|
+
],
|
|
25957
|
+
skills: [buildWriteRequirementSkill(paths, issueDefaults)],
|
|
25958
|
+
subAgents: [buildRequirementsWriterSubAgent(paths)],
|
|
25959
|
+
procedures: [nextRequirementIdProcedure],
|
|
25960
|
+
labels: [
|
|
25961
|
+
{
|
|
25962
|
+
name: "req:write",
|
|
25963
|
+
color: "FEF2C0",
|
|
25964
|
+
description: "Phase: write a formal requirement document using the requirements-writer skill"
|
|
25965
|
+
},
|
|
25966
|
+
{
|
|
25967
|
+
name: "tier:platform",
|
|
25968
|
+
color: "EDEDED",
|
|
25969
|
+
description: "Architectural tier: core platform (shared infrastructure, APIs, auth, tenant isolation)"
|
|
25970
|
+
},
|
|
25971
|
+
{
|
|
25972
|
+
name: "tier:industry",
|
|
25973
|
+
color: "EDEDED",
|
|
25974
|
+
description: "Architectural tier: industry vertical (capabilities not every tenant needs)"
|
|
25975
|
+
},
|
|
25976
|
+
{
|
|
25977
|
+
name: "tier:customer-workflow",
|
|
25978
|
+
color: "EDEDED",
|
|
25979
|
+
description: "Architectural tier: customer-configured workflow (business logic tenants configure)"
|
|
25980
|
+
},
|
|
25981
|
+
{
|
|
25982
|
+
name: "tier:consumer-app",
|
|
25983
|
+
color: "EDEDED",
|
|
25984
|
+
description: "Architectural tier: consumer application (UI/UX and integrations in external front-ends/systems)"
|
|
25985
|
+
}
|
|
25986
|
+
]
|
|
25987
|
+
};
|
|
25988
|
+
}
|
|
25989
|
+
var requirementsWriterBundle = buildRequirementsWriterBundle();
|
|
25990
|
+
|
|
25991
|
+
// src/agent/bundles/requirements-analyst.ts
|
|
25992
|
+
function buildRequirementsAnalystSubAgent(paths, issueDefaults) {
|
|
25993
|
+
return {
|
|
25994
|
+
name: "requirements-analyst",
|
|
25995
|
+
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.",
|
|
25996
|
+
model: AGENT_MODEL.POWERFUL,
|
|
25997
|
+
maxTurns: 80,
|
|
25998
|
+
platforms: { cursor: { exclude: true } },
|
|
25999
|
+
prompt: [
|
|
26000
|
+
"# Requirements Analyst Agent",
|
|
26001
|
+
"",
|
|
26002
|
+
"Dedicated agent loop for discovering requirement gaps from BCM (Business",
|
|
26003
|
+
"Capability Model) documents, product docs, and competitive analysis \u2014 then",
|
|
26004
|
+
"creating well-formed requirement issues for the downstream",
|
|
26005
|
+
"`requirements-writer` agent to draft. Designed for scheduled execution",
|
|
26006
|
+
"downstream of the BCM writer and company research agents.",
|
|
26007
|
+
"",
|
|
26008
|
+
"Follow your project's shared agent conventions (`AGENTS.md`,",
|
|
26009
|
+
"`CLAUDE.md`, or equivalent) for all commit, branch, and PR rules.",
|
|
26010
|
+
"",
|
|
26011
|
+
"---",
|
|
26012
|
+
"",
|
|
26013
|
+
...PROJECT_CONTEXT_MAINTAINER_SECTION,
|
|
26014
|
+
"## Design Principles",
|
|
26015
|
+
"",
|
|
26016
|
+
"1. **Discover, don't write.** This agent identifies *what requirements are",
|
|
26017
|
+
" missing*. The `requirements-writer` agent writes the actual documents.",
|
|
26018
|
+
" The boundary keeps this agent fast and the `requirements-writer`",
|
|
26019
|
+
" authoritative.",
|
|
26020
|
+
"2. **Trace everything.** Every discovered gap links to the source that",
|
|
26021
|
+
" revealed it (a BCM model doc, competitive analysis, product doc, or",
|
|
26022
|
+
" meeting extract).",
|
|
26023
|
+
"3. **Respect the taxonomy.** Route every discovered requirement to the",
|
|
26024
|
+
" correct BCM category (FR, BR, NFR, SEC, DR, INT, OPS, UX, MT, ADR, TR)",
|
|
26025
|
+
" using the shared disambiguation rules \u2014 the same taxonomy the",
|
|
26026
|
+
" `requirements-writer` and `requirements-reviewer` load. The",
|
|
26027
|
+
" canonical source lives in `requirements-taxonomy.ts` and is",
|
|
26028
|
+
" embedded verbatim in both downstream agents' prompts.",
|
|
26029
|
+
"4. **Deduplicate.** Before creating an issue, check whether a requirement",
|
|
26030
|
+
" already exists or an issue is already open for it.",
|
|
26031
|
+
"",
|
|
26032
|
+
"---",
|
|
26033
|
+
"",
|
|
26034
|
+
"## State Machine Overview",
|
|
26035
|
+
"",
|
|
26036
|
+
"Requirements synthesis flows through **2 phases**:",
|
|
26037
|
+
"",
|
|
26038
|
+
"```",
|
|
26039
|
+
"\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",
|
|
26040
|
+
"\u2502 1. SCAN \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 2. DRAFT-TRACE \u2502",
|
|
26041
|
+
"\u2502 Read docs, \u2502 \u2502 Write proposals,\u2502",
|
|
26042
|
+
"\u2502 identify \u2502 \u2502 create req:write\u2502",
|
|
26043
|
+
"\u2502 gaps, check \u2502 \u2502 issues, update \u2502",
|
|
26044
|
+
"\u2502 for dupes \u2502 \u2502 source docs \u2502",
|
|
26045
|
+
"\u2502 \u2502 \u2502 with traceability\u2502",
|
|
26046
|
+
"\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",
|
|
26047
|
+
"```",
|
|
26048
|
+
"",
|
|
26049
|
+
"This pipeline matches the `scan \u2192 draft-trace \u2192 write` pattern",
|
|
26050
|
+
"used by the sibling openhi project. Draft and Trace were previously",
|
|
26051
|
+
"separate phases; they were collapsed because the proposals file Draft",
|
|
26052
|
+
"wrote was only ever consumed by Trace (no human review, no async work",
|
|
26053
|
+
"between the two), so the phase boundary added latency without value.",
|
|
26054
|
+
"See ADR-009 for the full reasoning.",
|
|
26055
|
+
"",
|
|
26056
|
+
"**Issue labels encode the phase:**",
|
|
26057
|
+
"",
|
|
26058
|
+
"| Label | Phase | Session work |",
|
|
26059
|
+
"|-------|-------|-------------|",
|
|
26060
|
+
"| `req:scan` | 1. Scan | Read source docs, identify potential requirement gaps, check against existing requirements and open issues, write deduplicated scan report |",
|
|
26061
|
+
"| `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 |",
|
|
26062
|
+
"",
|
|
26063
|
+
"All issues also carry `type:requirement` and a `status:*` label.",
|
|
26064
|
+
"",
|
|
26065
|
+
"**Issue count per scan cycle:** 1 scan + 1 draft-trace = **2 sessions**.",
|
|
26066
|
+
"",
|
|
26067
|
+
"**Shortened paths:**",
|
|
26068
|
+
"- No gaps found after scan \u2192 skip draft-trace \u2192 **1 session**",
|
|
26069
|
+
"",
|
|
26070
|
+
"---",
|
|
26071
|
+
"",
|
|
26072
|
+
"## Configurable Paths",
|
|
26073
|
+
"",
|
|
26074
|
+
"Projects adopting this bundle must define these paths in their agent",
|
|
26075
|
+
"configuration (`agentConfig.rules` extension or project-level docs):",
|
|
26076
|
+
"",
|
|
26077
|
+
"| Placeholder | Meaning | Typical value |",
|
|
26078
|
+
"|-------------|---------|---------------|",
|
|
26079
|
+
`| \`<BCM_DOCS_ROOT>\` | Root of BCM model docs (capability models) | \`${paths.bcmRoot}/\` |`,
|
|
26080
|
+
`| \`<COMPETITIVE_ROOT>\` | Competitive analysis docs | \`${paths.docsRoot}/business-strategy/competitive/\` |`,
|
|
26081
|
+
`| \`<PRODUCT_ROOT>\` | Product roadmap / entity taxonomy | \`${paths.docsRoot}/product/\` |`,
|
|
26082
|
+
`| \`<MEETINGS_ROOT>\` | Meeting extracts | \`${paths.docsRoot}/research/meetings/\` |`,
|
|
26083
|
+
`| \`<RESEARCH_REQUIREMENTS_ROOT>\` | Scan reports and proposals | \`${paths.researchRequirementsRoot}/\` |`,
|
|
26084
|
+
`| \`<REQUIREMENTS_ROOT>\` | Final requirement documents (owned by requirements-writer) | \`${paths.requirementsRoot}/\` |`,
|
|
26085
|
+
"| `<PREFIX>` | Project-specific requirement ID prefix | e.g. `VRTX`, `ACME` |",
|
|
26086
|
+
"",
|
|
26087
|
+
"If your project stores these in different locations, substitute accordingly",
|
|
26088
|
+
"wherever the phase instructions reference a path.",
|
|
26089
|
+
"",
|
|
26090
|
+
"---",
|
|
26091
|
+
"",
|
|
26092
|
+
"## Agent Loop",
|
|
26093
|
+
"",
|
|
26094
|
+
"Run this loop exactly once per session. Never start a second issue.",
|
|
26095
|
+
"",
|
|
26096
|
+
"1. Claim one open `type:requirement` issue using phase priority:",
|
|
26097
|
+
" `req:scan` > `req:draft-trace`.",
|
|
26098
|
+
"2. Transition `status:ready` \u2192 `status:in-progress` and create the branch",
|
|
26099
|
+
" per your project's branch-naming convention.",
|
|
26100
|
+
"3. Execute the phase handler that matches the issue's `req:*` label.",
|
|
26101
|
+
"4. Commit, push, open a PR (if applicable), and close the issue per your",
|
|
26102
|
+
" project's PR workflow.",
|
|
26103
|
+
"",
|
|
26104
|
+
"---",
|
|
26105
|
+
"",
|
|
26106
|
+
"## Phase 1: Scan (`req:scan`)",
|
|
26107
|
+
"",
|
|
26108
|
+
"**Goal:** Read source documents, identify where requirements are missing,",
|
|
26109
|
+
"incomplete, or contradictory, then check each potential gap against existing",
|
|
26110
|
+
"requirements and open issues to eliminate duplicates. Produces a single",
|
|
26111
|
+
"deduplicated scan report.",
|
|
26112
|
+
"",
|
|
26113
|
+
"**Budget:** Reading source docs + reading requirement registries + searching",
|
|
26114
|
+
"issues. Write one deduplicated scan output file.",
|
|
26115
|
+
"",
|
|
26116
|
+
"### Scan Sources",
|
|
26117
|
+
"",
|
|
26118
|
+
"The issue specifies which source(s) to scan. Common scan scopes:",
|
|
26119
|
+
"",
|
|
26120
|
+
"| Scope | What to read | What to look for |",
|
|
26121
|
+
"|-------|-------------|-----------------|",
|
|
26122
|
+
`| **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. |`,
|
|
26123
|
+
"| **Competitive analysis** | One `comp-*.md` doc under `<COMPETITIVE_ROOT>` | Feature comparison gaps \u2014 competitor features the product lacks requirements for |",
|
|
26124
|
+
"| **Product roadmap** | `<PRODUCT_ROOT>/prioritized-feature-roadmap.md` | Roadmap items without corresponding FRs |",
|
|
26125
|
+
"| **Entity taxonomy** | `<PRODUCT_ROOT>/entity-taxonomy.md` | Entities without CRUD requirements (FR), data requirements (DR), or security requirements (SEC) |",
|
|
26126
|
+
"| **Meeting extract** | `<MEETINGS_ROOT>/meeting-*.extract.md` | Requirements identified but not yet formalized |",
|
|
26127
|
+
"",
|
|
26128
|
+
"### Steps",
|
|
26129
|
+
"",
|
|
26130
|
+
"1. **Read the source documents** specified in the issue.",
|
|
26131
|
+
"",
|
|
26132
|
+
"2. **Identify potential gaps.** For each potential missing requirement:",
|
|
26133
|
+
" - Classify into the correct BCM category (FR, BR, NFR, SEC, DR, INT,",
|
|
26134
|
+
" OPS, UX, MT, ADR, TR)",
|
|
26135
|
+
" - Apply the shared disambiguation rules (from `requirements-taxonomy.ts`,",
|
|
26136
|
+
" also embedded in the requirements-writer and requirements-reviewer",
|
|
26137
|
+
" prompts)",
|
|
26138
|
+
" - Note the source that revealed the gap",
|
|
26139
|
+
" - Estimate priority based on the source context",
|
|
26140
|
+
"",
|
|
26141
|
+
"3. **Read the requirements registry.** Scan `_index.md` files in each",
|
|
26142
|
+
" `<REQUIREMENTS_ROOT>/<category>/` directory to know what already exists.",
|
|
26143
|
+
"",
|
|
26144
|
+
"4. **Search for existing issues.** For each potential gap, search open issues:",
|
|
26145
|
+
" ```bash",
|
|
26146
|
+
' gh issue list --label "type:requirement" --state open \\',
|
|
26147
|
+
" --json number,title --limit 100",
|
|
26148
|
+
" ```",
|
|
26149
|
+
"",
|
|
26150
|
+
"5. **Classify each gap:**",
|
|
26151
|
+
" - **New** \u2014 no existing requirement or open issue covers this",
|
|
26152
|
+
" - **Duplicate** \u2014 an existing requirement already addresses this",
|
|
26153
|
+
" - **In progress** \u2014 an open issue already targets this",
|
|
26154
|
+
" - **Partial** \u2014 existing requirement partially covers this; note the gap",
|
|
26155
|
+
"",
|
|
26156
|
+
"6. **Write the deduplicated scan report** to:",
|
|
26157
|
+
" ```",
|
|
26158
|
+
" <RESEARCH_REQUIREMENTS_ROOT>/req-scan-<scope>-<YYYY-MM-DD>.md",
|
|
26159
|
+
" ```",
|
|
26160
|
+
"",
|
|
26161
|
+
" Format:",
|
|
26162
|
+
" ```markdown",
|
|
26163
|
+
" ---",
|
|
26164
|
+
' title: "Requirements Scan: <scope>"',
|
|
26165
|
+
" date: YYYY-MM-DD",
|
|
26166
|
+
" parent_issue: <N>",
|
|
26167
|
+
" status: complete",
|
|
26168
|
+
" ---",
|
|
26169
|
+
"",
|
|
26170
|
+
" ## Source Documents Reviewed",
|
|
26171
|
+
" - <path> \u2014 <brief description>",
|
|
26172
|
+
"",
|
|
26173
|
+
" ## Existing Requirements Checked",
|
|
26174
|
+
" - <category>: <count> existing docs, <count> open issues",
|
|
26175
|
+
"",
|
|
26176
|
+
" ## Identified Gaps (New)",
|
|
26177
|
+
" ### Gap 1: <Title>",
|
|
26178
|
+
" - **Category:** FR / BR / NFR / SEC / DR / INT / OPS / UX / MT / ADR / TR",
|
|
26179
|
+
" - **Source:** <path to doc + section that revealed this gap>",
|
|
26180
|
+
" - **Priority:** High / Normal / Low",
|
|
26181
|
+
" - **Rationale:** <why this requirement is needed>",
|
|
26182
|
+
" - **Duplicate check:** No existing requirement or open issue found",
|
|
26183
|
+
" - **Proposed scope:** <1-2 sentences on what the requirement should cover>",
|
|
26184
|
+
"",
|
|
26185
|
+
" ## Already Covered",
|
|
26186
|
+
" <list of potential gaps that turned out to already have requirements>",
|
|
26187
|
+
"",
|
|
26188
|
+
" ## In Progress",
|
|
26189
|
+
" <gaps that already have open issues \u2014 include issue numbers>",
|
|
26190
|
+
"",
|
|
26191
|
+
" ## Ambiguous / Needs Human Decision",
|
|
26192
|
+
" <gaps where the correct category or scope is unclear>",
|
|
26193
|
+
" ```",
|
|
26194
|
+
"",
|
|
26195
|
+
"7. **Create downstream issues based on findings:**",
|
|
26196
|
+
" - If any new gaps were identified \u2192 create `req:draft-trace` issue",
|
|
26197
|
+
" (blocked on this issue via `Depends on: #N`).",
|
|
26198
|
+
" - If **no gaps** were found \u2192 stop (no further phases needed). Comment",
|
|
26199
|
+
" on the issue noting that no gaps were identified, and proceed directly",
|
|
26200
|
+
" to commit and push. The scan issue will be marked done with no",
|
|
26201
|
+
" downstream work needed.",
|
|
26202
|
+
"",
|
|
26203
|
+
"8. **Commit and push.**",
|
|
26204
|
+
"",
|
|
26205
|
+
"---",
|
|
26206
|
+
"",
|
|
26207
|
+
"## Phase 2: Draft & Trace (`req:draft-trace`)",
|
|
26208
|
+
"",
|
|
26209
|
+
"**Goal:** Expand each identified gap into a requirement proposal, create",
|
|
26210
|
+
"a `req:write` GitHub issue for each proposal so the downstream",
|
|
26211
|
+
"`requirements-writer` bundle picks it up, and backfill source-document",
|
|
26212
|
+
"traceability notes \u2014 all in a single session.",
|
|
26213
|
+
"",
|
|
26214
|
+
"**Budget:** No web searches. Reading + writing proposals + issue creation",
|
|
26215
|
+
"+ minor traceability edits to source documents.",
|
|
26216
|
+
"",
|
|
26217
|
+
"Draft and Trace were previously separate phases. They were collapsed",
|
|
26218
|
+
"because the proposals file written by Draft was only ever consumed by",
|
|
26219
|
+
"Trace \u2014 no human review, no async work, no CI validation sat between",
|
|
26220
|
+
"them. See ADR-009 for the decision record.",
|
|
25764
26221
|
"",
|
|
25765
|
-
"
|
|
26222
|
+
"### `req:write` issue schema",
|
|
25766
26223
|
"",
|
|
25767
|
-
"
|
|
25768
|
-
"
|
|
25769
|
-
"
|
|
25770
|
-
"
|
|
25771
|
-
"
|
|
25772
|
-
"
|
|
26224
|
+
"The `req:write` issues this phase creates are picked up by the",
|
|
26225
|
+
"downstream `requirements-writer` agent, which parses a strict",
|
|
26226
|
+
"schema on intake. The authoritative schema is defined in the",
|
|
26227
|
+
"`requirements-writer` sub-agent prompt under **The `req:write`",
|
|
26228
|
+
"Issue Schema** \u2014 every `req:write` issue this phase opens must",
|
|
26229
|
+
"conform. The same schema is embedded in the",
|
|
26230
|
+
"`requirements-reviewer` follow-up generator so the three",
|
|
26231
|
+
"producers never drift apart.",
|
|
25773
26232
|
"",
|
|
25774
26233
|
...REQ_WRITE_ISSUE_SCHEMA_SECTION,
|
|
25775
26234
|
"",
|
|
25776
|
-
"
|
|
25777
|
-
"",
|
|
25778
|
-
"
|
|
26235
|
+
"**Deriving the three required fields from the proposal.** For",
|
|
26236
|
+
"every `req:write` issue this phase opens, the three fields come",
|
|
26237
|
+
"directly from the proposal entry written earlier in the same",
|
|
26238
|
+
"session:",
|
|
25779
26239
|
"",
|
|
25780
|
-
"**
|
|
25781
|
-
"
|
|
25782
|
-
"
|
|
26240
|
+
"- **Category** \u2014 the proposal's `**Category:**` line.",
|
|
26241
|
+
"- **Tier** \u2014 the proposal's `**Tier:**` line.",
|
|
26242
|
+
"- **Output Path** \u2014",
|
|
26243
|
+
" `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`,",
|
|
26244
|
+
" using the sequence number determined in the proposal step below.",
|
|
25783
26245
|
"",
|
|
25784
|
-
"**
|
|
25785
|
-
"
|
|
25786
|
-
"
|
|
25787
|
-
"
|
|
25788
|
-
"
|
|
26246
|
+
"**Validation step \u2014 required before opening the issue.** Before",
|
|
26247
|
+
"calling `gh issue create`, verify all three required fields are",
|
|
26248
|
+
"populated and internally consistent (Category matches title",
|
|
26249
|
+
"prefix, Tier matches `tier:*` label, Output Path filename starts",
|
|
26250
|
+
"with the Category prefix). If any field cannot be derived, open",
|
|
26251
|
+
"the issue with `status:needs-attention` (not `status:ready`) and",
|
|
26252
|
+
"include a `Missing: <field> \u2014 <one-line reason>` line so a human",
|
|
26253
|
+
"triaging the issue only has to supply the remaining value(s)",
|
|
26254
|
+
"before flipping the label to `status:ready`.",
|
|
25789
26255
|
"",
|
|
25790
26256
|
"### Steps",
|
|
25791
26257
|
"",
|
|
25792
|
-
"1. **
|
|
25793
|
-
" authoritative schema in the",
|
|
25794
|
-
" [The `req:write` Issue Schema](#the-reqwrite-issue-schema)",
|
|
25795
|
-
" section below. Before writing, verify every required field",
|
|
25796
|
-
" (Category, Tier, Output Path) is present and internally",
|
|
25797
|
-
" consistent:",
|
|
26258
|
+
"1. **Read the scan report** from Phase 1.",
|
|
25798
26259
|
"",
|
|
25799
|
-
"
|
|
25800
|
-
" the Output Path filename.",
|
|
25801
|
-
" - Tier matches the `tier:*` label applied to the issue.",
|
|
25802
|
-
" - Output Path sits under",
|
|
25803
|
-
" `<REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`.",
|
|
26260
|
+
"2. **For each gap**, write a detailed proposal:",
|
|
25804
26261
|
"",
|
|
25805
|
-
"
|
|
25806
|
-
"
|
|
25807
|
-
" (upstream could not derive it), comment on the issue with",
|
|
25808
|
-
" which field is wrong, ensure `status:needs-attention` is set,",
|
|
25809
|
-
" and stop without writing. A human triages before the writer",
|
|
25810
|
-
" tries again.",
|
|
26262
|
+
" ```markdown",
|
|
26263
|
+
" ## Proposed: <PREFIX>-<NNN> \u2014 <Title>",
|
|
25811
26264
|
"",
|
|
25812
|
-
"
|
|
25813
|
-
"
|
|
25814
|
-
"
|
|
25815
|
-
"
|
|
25816
|
-
" invent fields it omits.",
|
|
26265
|
+
" **Category:** <FR/BR/NFR/SEC/DR/INT/OPS/UX/MT/ADR/TR>",
|
|
26266
|
+
" **Tier:** <platform/industry/customer-workflow/consumer-app>",
|
|
26267
|
+
" **Priority:** <High/Normal/Low>",
|
|
26268
|
+
" **Source:** <document path and section>",
|
|
25817
26269
|
"",
|
|
25818
|
-
"
|
|
25819
|
-
"
|
|
25820
|
-
" INT, ADR, or TR \u2014 these depend on the standards reference for",
|
|
25821
|
-
" correct framing.",
|
|
26270
|
+
" ### Summary",
|
|
26271
|
+
" <2-3 sentences describing what the requirement should capture>",
|
|
25822
26272
|
"",
|
|
25823
|
-
"
|
|
25824
|
-
"
|
|
25825
|
-
"
|
|
26273
|
+
" ### Draft Acceptance Criteria",
|
|
26274
|
+
" - [ ] <testable criterion 1>",
|
|
26275
|
+
" - [ ] <testable criterion 2>",
|
|
26276
|
+
" - [ ] <testable criterion 3>",
|
|
25826
26277
|
"",
|
|
25827
|
-
"
|
|
25828
|
-
"
|
|
25829
|
-
"
|
|
25830
|
-
"
|
|
25831
|
-
"
|
|
25832
|
-
"
|
|
25833
|
-
"
|
|
25834
|
-
"
|
|
26278
|
+
" ### Traceability",
|
|
26279
|
+
" - **Implements:** <BR or parent requirement if applicable>",
|
|
26280
|
+
" - **Related:** list each interacting requirement on its own",
|
|
26281
|
+
" `- <PREFIX>-<NNN>` line in ascending ID order (append-only \u2014",
|
|
26282
|
+
" add your own entries, never rewrite or collapse existing ones",
|
|
26283
|
+
" into a comma-separated line), not a single comma-joined line",
|
|
26284
|
+
" - **Source:** <BCM doc, competitive analysis, or meeting that revealed",
|
|
26285
|
+
" the gap \u2014 use a markdown link. If the source is a meeting note, the",
|
|
26286
|
+
" downstream requirement doc must include the same meeting as a link in",
|
|
26287
|
+
" its Traceability `Related:` list.>",
|
|
25835
26288
|
"",
|
|
25836
|
-
"
|
|
25837
|
-
"
|
|
25838
|
-
"
|
|
25839
|
-
"
|
|
26289
|
+
" ### Decision Authority",
|
|
26290
|
+
' <"Direct write" for BR/FR/NFR/SEC/UX, or "Proposed \u2014 needs human',
|
|
26291
|
+
' decision" for ADR/TR, or "Mixed \u2014 defer technology choices" for',
|
|
26292
|
+
" DR/MT/INT/OPS>",
|
|
25840
26293
|
"",
|
|
25841
|
-
"
|
|
25842
|
-
"
|
|
25843
|
-
|
|
25844
|
-
"
|
|
26294
|
+
" ### Notes for Requirements Writer",
|
|
26295
|
+
" <any context the writer should know \u2014 related ADRs, existing partial",
|
|
26296
|
+
" coverage, relevant competitive features>",
|
|
26297
|
+
" ```",
|
|
25845
26298
|
"",
|
|
25846
|
-
"
|
|
25847
|
-
"
|
|
25848
|
-
"
|
|
25849
|
-
"
|
|
26299
|
+
"3. **Write the proposals** to:",
|
|
26300
|
+
" ```",
|
|
26301
|
+
" <RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<YYYY-MM-DD>.md",
|
|
26302
|
+
" ```",
|
|
25850
26303
|
"",
|
|
25851
|
-
"
|
|
25852
|
-
"
|
|
25853
|
-
"
|
|
25854
|
-
" the
|
|
26304
|
+
"4. **Determine next sequence numbers.** For the **first** proposal",
|
|
26305
|
+
" in a given category this session, run",
|
|
26306
|
+
" `.claude/procedures/next-requirement-id.sh <PREFIX> <category-dir>`",
|
|
26307
|
+
" (no issue number \u2014 the `req:write` issues are not filed yet) to",
|
|
26308
|
+
" get the starting ID from live state (existing docs plus open",
|
|
26309
|
+
" `req:write` issues). For each subsequent proposal in the **same**",
|
|
26310
|
+
" category within this single batch session, increment the ID",
|
|
26311
|
+
" locally (the freshly-filed issues from this batch are not yet",
|
|
26312
|
+
" reflected in the procedure's live-state query). These IDs are",
|
|
26313
|
+
" **hints** stamped into the issue body; the downstream writer",
|
|
26314
|
+
" re-validates and finalizes each ID at write-start, so a residual",
|
|
26315
|
+
" collision costs nothing. If the procedure is unavailable, fall",
|
|
26316
|
+
" back to scanning `<REQUIREMENTS_ROOT>/<category>/` for the next",
|
|
26317
|
+
" available `NNN`.",
|
|
25855
26318
|
"",
|
|
25856
|
-
"
|
|
25857
|
-
"
|
|
25858
|
-
"
|
|
25859
|
-
"
|
|
25860
|
-
" - [ ] File name follows `{PREFIX}-{NNN}-{slug}.md`",
|
|
25861
|
-
" - [ ] Status is `Draft` for direct-write categories or `Proposed`",
|
|
25862
|
-
" for ADR/TR",
|
|
25863
|
-
" - [ ] Every template section is present (use `TODO:` or `Not",
|
|
25864
|
-
" applicable \u2014 <reason>` for unfilled sections)",
|
|
25865
|
-
" - [ ] `## Traceability` exists with at least one upstream link",
|
|
25866
|
-
" - [ ] No technology decisions made in direct-write categories \u2014",
|
|
25867
|
-
" deferred to `Proposed` ADR/TR with cross-links in Open",
|
|
25868
|
-
" Items",
|
|
25869
|
-
" - [ ] `## Open Items` is present with Identified Gaps,",
|
|
25870
|
-
" Follow-up Questions, and Contradictions subsections",
|
|
25871
|
-
" - [ ] ADR/TR documents include a Recommendation section and an",
|
|
25872
|
-
" Open Item flagging the human decision required",
|
|
25873
|
-
" - [ ] Category index updated with a row for the new document",
|
|
25874
|
-
" - [ ] Tier value matches the issue's `tier:*` label",
|
|
25875
|
-
" - [ ] Cross-tier traceability entries exist where the document",
|
|
25876
|
-
" depends on or enables a different tier",
|
|
26319
|
+
"5. **Create requirement issues.** For each proposal, file a",
|
|
26320
|
+
" `req:write` issue using the canonical recipe documented in",
|
|
26321
|
+
" `## Template: req:write` of",
|
|
26322
|
+
" `docs/src/content/docs/agents/issue-templates.md`.",
|
|
25877
26323
|
"",
|
|
25878
|
-
|
|
25879
|
-
"
|
|
26324
|
+
` All \`type:requirement\` issues default to \`priority:${labelsForPhase(issueDefaults, "req:write").priority}\` (override`,
|
|
26325
|
+
" only if the proposal's priority was explicitly High or Low). Each",
|
|
26326
|
+
" issue must also carry the `req:write` phase label plus the matching",
|
|
26327
|
+
" `tier:*` label so the downstream `requirements-writer` bundle picks",
|
|
26328
|
+
" it up with the correct tier. Concretely, the label list includes",
|
|
26329
|
+
' `--label "type:requirement"`, `--label "req:write"`,',
|
|
26330
|
+
` \`--label "tier:<tier-slug>"\`, \`--label "status:${labelsForPhase(issueDefaults, "req:write").status}"\`, and`,
|
|
26331
|
+
` \`--label "priority:${labelsForPhase(issueDefaults, "req:write").priority}"\`.`,
|
|
25880
26332
|
"",
|
|
25881
|
-
"
|
|
26333
|
+
" The body must carry the three writer-required fields in an",
|
|
26334
|
+
" `## Objective` block, written as bold-prefixed lines so the",
|
|
26335
|
+
" writer's intake parser can pull them out, plus the reserved-ID",
|
|
26336
|
+
" hint from step 4:",
|
|
25882
26337
|
"",
|
|
25883
|
-
"
|
|
26338
|
+
" - `**Category:** <BR/FR/NFR/TR/ADR/SEC/DR/INT/OPS/UX/MT>`",
|
|
26339
|
+
" - `**Tier:** <platform/industry/customer-workflow/consumer-app>`",
|
|
26340
|
+
" - `**Output Path:** <REQUIREMENTS_ROOT>/<category-dir>/<PREFIX>-<NNN>-<slug>.md`",
|
|
26341
|
+
" - `**Requirement ID:** <PREFIX>-<NNN>` \u2014 the ID reserved in step 4",
|
|
26342
|
+
" (must match the `<PREFIX>-<NNN>` in the Output Path). This is a",
|
|
26343
|
+
" **hint**: the downstream writer re-validates and finalizes the",
|
|
26344
|
+
" ID at write-start, so a residual collision is repaired there",
|
|
26345
|
+
" for free.",
|
|
25884
26346
|
"",
|
|
25885
|
-
"
|
|
26347
|
+
" Then under `## Inputs / Read`, list the proposals file",
|
|
26348
|
+
" (`<RESEARCH_REQUIREMENTS_ROOT>/req-proposals-<scope>-<date>.md`)",
|
|
26349
|
+
" and any source documents the writer should consult (BCM model",
|
|
26350
|
+
" docs, competitive analyses, product docs).",
|
|
25886
26351
|
"",
|
|
25887
|
-
"
|
|
25888
|
-
"
|
|
25889
|
-
|
|
25890
|
-
"
|
|
25891
|
-
"
|
|
25892
|
-
"
|
|
25893
|
-
"
|
|
26352
|
+
" When one of Category / Tier / Output Path could not be derived",
|
|
26353
|
+
" from the proposal (for example, the proposal omitted the Tier",
|
|
26354
|
+
` line), replace \`status:${labelsForPhase(issueDefaults, "req:write").status}\` with \`status:needs-attention\` in`,
|
|
26355
|
+
" the label list and add a `Missing: <field> \u2014 <reason>` line",
|
|
26356
|
+
" directly below the Output Path line in the body. Populate",
|
|
26357
|
+
" whichever of the three fields **could** be derived so a human",
|
|
26358
|
+
" triaging the issue has the minimum possible cleanup.",
|
|
25894
26359
|
"",
|
|
25895
|
-
"
|
|
25896
|
-
"
|
|
25897
|
-
"
|
|
25898
|
-
"
|
|
26360
|
+
"6. **Update source documents.** In each BCM model doc or competitive",
|
|
26361
|
+
" analysis that was scanned, add a note in the project-relevance /",
|
|
26362
|
+
" strategic-implications section (whichever heading the source doc uses)",
|
|
26363
|
+
" indicating that a requirement issue was created. Each note is its",
|
|
26364
|
+
" own single line:",
|
|
25899
26365
|
"",
|
|
25900
|
-
"
|
|
25901
|
-
"-
|
|
25902
|
-
"
|
|
25903
|
-
"
|
|
25904
|
-
"- `people:*`, `company:*`, `software:*`, `research:*`, or",
|
|
25905
|
-
" `industry:*` issues \u2014 those belong to their respective bundles",
|
|
26366
|
+
" ```markdown",
|
|
26367
|
+
" - Gap addressed: see [<PREFIX>-<NNN>](<relative path to requirement doc>)",
|
|
26368
|
+
" (issue #<N>)",
|
|
26369
|
+
" ```",
|
|
25906
26370
|
"",
|
|
25907
|
-
"
|
|
25908
|
-
"
|
|
25909
|
-
"
|
|
26371
|
+
" These `Gap addressed:` notes form an **append-only,",
|
|
26372
|
+
" one-per-line, deterministically sorted** list \u2014 the shared BCM /",
|
|
26373
|
+
" competitive-analysis source docs are edited concurrently by",
|
|
26374
|
+
" sibling `req:draft-trace` sessions, so this list is a",
|
|
26375
|
+
" merge-conflict hotspot. Maintain it under the same discipline",
|
|
26376
|
+
" the `shared-editing-safety` rule prescribes for shared registries,",
|
|
26377
|
+
" applied here to this one list section (the surrounding source doc",
|
|
26378
|
+
" is a hand-authored document, not a registry, so only this list is",
|
|
26379
|
+
" governed):",
|
|
26380
|
+
"",
|
|
26381
|
+
" - **One entry per line.** Add each `- Gap addressed:` note as its",
|
|
26382
|
+
" own new bullet. Never collapse notes into a single",
|
|
26383
|
+
" comma-separated line \u2014 a single mutable line always conflicts",
|
|
26384
|
+
" when two sessions touch it.",
|
|
26385
|
+
" - **Ascending order.** Insert your bullet in the position that",
|
|
26386
|
+
" keeps the list sorted by requirement ID (`<PREFIX>-<NNN>`), so",
|
|
26387
|
+
" concurrent sibling inserts land on different lines and git",
|
|
26388
|
+
" auto-merges them.",
|
|
26389
|
+
" - **Append-only.** Never rewrite, reorder, or merge existing",
|
|
26390
|
+
" notes; add only your own.",
|
|
26391
|
+
" - **Defer the commit of this section to the final pre-push",
|
|
26392
|
+
" step.** Pull the latest default branch before inserting, insert",
|
|
26393
|
+
" only your own note, and if a concurrent sibling already landed a",
|
|
26394
|
+
" bullet, rebase and re-insert. Commit this shared list section on",
|
|
26395
|
+
" its own at the end, then push immediately.",
|
|
26396
|
+
"",
|
|
26397
|
+
"7. **Comment on the scan issue** with a summary of all issues created and",
|
|
26398
|
+
" all docs updated.",
|
|
26399
|
+
"",
|
|
26400
|
+
"8. **Commit and push.**",
|
|
25910
26401
|
"",
|
|
25911
26402
|
"---",
|
|
25912
26403
|
"",
|
|
@@ -25914,157 +26405,118 @@ function buildRequirementsWriterSubAgent(paths) {
|
|
|
25914
26405
|
"",
|
|
25915
26406
|
"| Direction | Agent | What |",
|
|
25916
26407
|
"|-----------|-------|------|",
|
|
25917
|
-
|
|
25918
|
-
"| Upstream |
|
|
25919
|
-
"|
|
|
26408
|
+
`| Upstream | BCM Writer | Scans capability-model docs for project-relevance gaps (judged against \`${paths.docsRoot}/project-context.md\`) |`,
|
|
26409
|
+
"| Upstream | Company Research | Scans competitive analysis for feature comparison gaps |",
|
|
26410
|
+
"| Upstream | Meeting Analyst | Scans meeting extracts for requirement proposals |",
|
|
26411
|
+
"| Downstream | `requirements-writer` | Picks up the `type:requirement` + `req:write` issues this agent creates and drafts the actual requirement document |",
|
|
25920
26412
|
"",
|
|
25921
|
-
"**File boundaries:**
|
|
25922
|
-
"
|
|
25923
|
-
"`<REQUIREMENTS_ROOT
|
|
25924
|
-
"
|
|
26413
|
+
"**File boundaries:** Writes to `<RESEARCH_REQUIREMENTS_ROOT>/req-*.md` and",
|
|
26414
|
+
"minor traceability edits to `<BCM_DOCS_ROOT>` and `<COMPETITIVE_ROOT>`.",
|
|
26415
|
+
"Never writes to `<REQUIREMENTS_ROOT>/` \u2014 that is owned by the",
|
|
26416
|
+
"requirements-writer agent.",
|
|
26417
|
+
"",
|
|
26418
|
+
"---",
|
|
26419
|
+
"",
|
|
26420
|
+
"## Blocked Issues",
|
|
26421
|
+
"",
|
|
26422
|
+
"Additional block reasons specific to requirements synthesis:",
|
|
26423
|
+
"- Source document has unresolved contradictions",
|
|
26424
|
+
"- Category classification is ambiguous (needs human disambiguation)",
|
|
26425
|
+
"- Dependent BCM documents are still in draft with placeholder content",
|
|
25925
26426
|
"",
|
|
25926
26427
|
"---",
|
|
25927
26428
|
"",
|
|
25928
26429
|
"## Rules",
|
|
25929
26430
|
"",
|
|
25930
|
-
"- **
|
|
25931
|
-
"
|
|
25932
|
-
"
|
|
25933
|
-
"
|
|
25934
|
-
"
|
|
25935
|
-
"- **
|
|
25936
|
-
"
|
|
25937
|
-
"
|
|
25938
|
-
"
|
|
25939
|
-
"
|
|
25940
|
-
"
|
|
25941
|
-
"
|
|
25942
|
-
"
|
|
25943
|
-
" the source documents the proposal cited, and the BCM capability",
|
|
25944
|
-
" it supports (when applicable).",
|
|
25945
|
-
"- **Update the category index every time.** A requirement that",
|
|
25946
|
-
" exists as a file but is missing from its category index is",
|
|
25947
|
-
" invisible to anyone browsing the documentation tree.",
|
|
25948
|
-
"- **Write requirements, not capability models or gap reports.**",
|
|
25949
|
-
" Never open `req:scan`, `req:draft-trace`, or `bcm:*` issues from",
|
|
25950
|
-
" this pipeline."
|
|
26431
|
+
"- **Discover, don't write requirements.** Create issues for the",
|
|
26432
|
+
" `requirements-writer` agent \u2014 don't write requirement documents",
|
|
26433
|
+
" directly.",
|
|
26434
|
+
"- **Deduplicate rigorously.** Check both existing docs and open issues",
|
|
26435
|
+
" before flagging a gap.",
|
|
26436
|
+
"- **Respect decision authority.** Mark ADR/TR proposals as needing human",
|
|
26437
|
+
" decision. Don't create direct-write issues for technology choices.",
|
|
26438
|
+
"- **Bidirectional traceability.** Every `req-scan-*.md` and",
|
|
26439
|
+
" `req-proposals-*.md` must include a `## Produced` section listing the",
|
|
26440
|
+
" downstream requirement issues (and eventual requirement documents) it",
|
|
26441
|
+
" spawned, as markdown links; each formal requirement document under",
|
|
26442
|
+
" `<REQUIREMENTS_ROOT>/` must include a forward link back to the scan or",
|
|
26443
|
+
" proposal that produced it."
|
|
25951
26444
|
].join("\n")
|
|
25952
26445
|
};
|
|
25953
26446
|
}
|
|
25954
|
-
function
|
|
26447
|
+
function buildScanRequirementsSkill(issueDefaults) {
|
|
25955
26448
|
return {
|
|
25956
|
-
name:
|
|
25957
|
-
description: "
|
|
26449
|
+
name: "scan-requirements",
|
|
26450
|
+
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.",
|
|
25958
26451
|
disableModelInvocation: true,
|
|
25959
26452
|
userInvocable: true,
|
|
25960
26453
|
context: "fork",
|
|
25961
|
-
agent: "requirements-
|
|
26454
|
+
agent: "requirements-analyst",
|
|
25962
26455
|
platforms: { cursor: { exclude: true } },
|
|
25963
|
-
referenceFiles: buildRequirementsWriterReferenceFiles(paths),
|
|
25964
26456
|
instructions: [
|
|
25965
|
-
"#
|
|
26457
|
+
"# Scan Requirements",
|
|
25966
26458
|
"",
|
|
25967
|
-
"
|
|
25968
|
-
"
|
|
25969
|
-
"
|
|
25970
|
-
"Dispatches the `requirements-writer` agent.",
|
|
26459
|
+
"Kick off a requirements-analyst scan cycle. Creates a `req:scan` issue",
|
|
26460
|
+
"targeted at the requested scope and dispatches Phase 1 (Scan) in the",
|
|
26461
|
+
"requirements-analyst agent.",
|
|
25971
26462
|
"",
|
|
25972
26463
|
"## Usage",
|
|
25973
26464
|
"",
|
|
25974
|
-
"/
|
|
25975
|
-
"",
|
|
25976
|
-
"Where `<category>` is one of `BR`, `FR`, `NFR`, `TR`, `ADR`, `SEC`,",
|
|
25977
|
-
"`DR`, `INT`, `OPS`, `UX`, `MT`.",
|
|
25978
|
-
"",
|
|
25979
|
-
"Optional extensions in the issue body:",
|
|
25980
|
-
"- `tier: platform | industry | customer-workflow | consumer-app` \u2014",
|
|
25981
|
-
" the architectural tier (default: `platform`)",
|
|
25982
|
-
"- `prefix: <PROJECT_PREFIX>` \u2014 override the default category prefix",
|
|
25983
|
-
` with a project-specific one declared in \`${paths.docsRoot}/project-context.md\``,
|
|
25984
|
-
"- `customer: <link-or-slug>` \u2014 link the requirement to a customer",
|
|
25985
|
-
" profile (expected for Customer Workflow / Consumer Application",
|
|
25986
|
-
" tiers in projects that track customer profiles)",
|
|
25987
|
-
"- `proposal: <path>` \u2014 pin the upstream proposal file under",
|
|
25988
|
-
" `<RESEARCH_REQUIREMENTS_ROOT>` (default: derived from the issue",
|
|
25989
|
-
" context)",
|
|
25990
|
-
"- `output: <path>` \u2014 override the default Output Path",
|
|
25991
|
-
"",
|
|
25992
|
-
"## Default Paths",
|
|
25993
|
-
"",
|
|
25994
|
-
`If the project has no override in \`${paths.docsRoot}/project-context.md\` or`,
|
|
25995
|
-
"`agentConfig.rules`, outputs land under:",
|
|
25996
|
-
"",
|
|
25997
|
-
`- \`${paths.requirementsRoot}/<category-dir>/<PREFIX>-<NNN>-<slug>.md\``,
|
|
25998
|
-
`- \`${paths.requirementsRoot}/<category-dir>/README.md\` (registry update)`,
|
|
25999
|
-
`- \`${paths.requirementsRoot}/README.md\` (top-level README, generated on`,
|
|
26000
|
-
" first use only)",
|
|
26465
|
+
"/scan-requirements <scope>",
|
|
26001
26466
|
"",
|
|
26002
|
-
"
|
|
26003
|
-
"`
|
|
26467
|
+
"Where `<scope>` is one of:",
|
|
26468
|
+
"- `bcm:<PREFIX-NNN>` \u2014 a single BCM model doc",
|
|
26469
|
+
"- `competitive:<slug>` \u2014 a single competitive analysis doc",
|
|
26470
|
+
"- `product-roadmap` \u2014 the prioritized feature roadmap",
|
|
26471
|
+
"- `entity-taxonomy` \u2014 the entity taxonomy doc",
|
|
26472
|
+
"- `meeting:<slug>` \u2014 a meeting extract",
|
|
26473
|
+
"- `all-bcm` / `all-competitive` \u2014 full sweep (long-running)",
|
|
26004
26474
|
"",
|
|
26005
26475
|
"## Steps",
|
|
26006
26476
|
"",
|
|
26007
|
-
|
|
26008
|
-
`
|
|
26009
|
-
"
|
|
26010
|
-
"
|
|
26011
|
-
" proposals file exists).",
|
|
26012
|
-
"2. Execute the write phase of the requirements-writer agent.",
|
|
26013
|
-
"3. The agent writes one requirement document, updates the category",
|
|
26014
|
-
" index, opens a PR, and closes the issue.",
|
|
26477
|
+
`1. Create a \`req:scan\` issue with \`type:requirement\`, \`priority:${labelsForPhase(issueDefaults, "req:scan").priority}\`,`,
|
|
26478
|
+
` and \`status:${labelsForPhase(issueDefaults, "req:scan").status}\`. Body must list the files to read and the scan scope.`,
|
|
26479
|
+
"2. Execute Phase 1 (Scan) of the requirements-analyst agent.",
|
|
26480
|
+
"3. If gaps are found, a `req:draft-trace` issue is created automatically.",
|
|
26015
26481
|
"",
|
|
26016
26482
|
"## Output",
|
|
26017
26483
|
"",
|
|
26018
|
-
"-
|
|
26019
|
-
"
|
|
26020
|
-
"
|
|
26021
|
-
"- A category-index row pointing at the new document",
|
|
26022
|
-
"- (First-time only) a top-level requirements README derived from",
|
|
26023
|
-
" `_template-requirements-README.md`"
|
|
26484
|
+
"- A `req-scan-<scope>-<YYYY-MM-DD>.md` file under the project's research",
|
|
26485
|
+
" requirements directory.",
|
|
26486
|
+
"- A `req:draft-trace` issue if any gaps were identified."
|
|
26024
26487
|
].join("\n")
|
|
26025
26488
|
};
|
|
26026
26489
|
}
|
|
26027
|
-
function
|
|
26490
|
+
function buildRequirementsAnalystBundle(paths = DEFAULT_AGENT_PATHS, issueDefaults = DEFAULT_RESOLVED_ISSUE_DEFAULTS) {
|
|
26028
26491
|
return {
|
|
26029
|
-
name: "requirements-
|
|
26030
|
-
description: "Requirements
|
|
26492
|
+
name: "requirements-analyst",
|
|
26493
|
+
description: "Requirements gap-discovery agent bundle for BCM-driven projects. 2-phase pipeline (scan, draft-trace) with req:* phase labels.",
|
|
26031
26494
|
appliesWhen: () => true,
|
|
26032
26495
|
rules: [
|
|
26033
26496
|
{
|
|
26034
|
-
name: "requirements-
|
|
26035
|
-
description: "Describes the requirements-
|
|
26497
|
+
name: "requirements-analyst-workflow",
|
|
26498
|
+
description: "Describes the 2-phase requirements gap-discovery pipeline, the req:* label taxonomy, and the boundary with the downstream requirements-writer agent.",
|
|
26036
26499
|
scope: AGENT_RULE_SCOPE.ALWAYS,
|
|
26037
26500
|
content: [
|
|
26038
|
-
"# Requirements
|
|
26039
|
-
"",
|
|
26040
|
-
"Use `/write-requirement <category> <short-title>` to author one",
|
|
26041
|
-
"formal requirement document. The writer runs in a single phase",
|
|
26042
|
-
"tracked by a GitHub issue labeled `req:write` plus the matching",
|
|
26043
|
-
"`tier:*` label. Issues also carry `type:requirement` (declared",
|
|
26044
|
-
"by the upstream `requirements-analyst` bundle).",
|
|
26045
|
-
"",
|
|
26046
|
-
"The pipeline produces **requirement documents only** \u2014 capability",
|
|
26047
|
-
"models are written by the `bcm-writer` agent and gap discovery is",
|
|
26048
|
-
"the responsibility of the `requirements-analyst` agent. The",
|
|
26049
|
-
"writer never opens `req:scan`, `req:draft-trace`, or `bcm:*`",
|
|
26050
|
-
"issues.",
|
|
26501
|
+
"# Requirements Analyst Workflow",
|
|
26051
26502
|
"",
|
|
26052
|
-
"
|
|
26053
|
-
"
|
|
26054
|
-
"
|
|
26055
|
-
"
|
|
26056
|
-
"
|
|
26057
|
-
"the bundle.",
|
|
26503
|
+
"Use `/scan-requirements <scope>` to kick off a requirements gap",
|
|
26504
|
+
"discovery cycle. The pipeline runs in 2 phases \u2014 scan and",
|
|
26505
|
+
"draft-trace \u2014 each tracked by its own GitHub issue labeled",
|
|
26506
|
+
"`req:scan` or `req:draft-trace`. All issues carry",
|
|
26507
|
+
"`type:requirement`.",
|
|
26058
26508
|
"",
|
|
26059
|
-
"
|
|
26060
|
-
"
|
|
26061
|
-
"
|
|
26062
|
-
"
|
|
26063
|
-
"
|
|
26509
|
+
"The requirements-analyst *discovers gaps, drafts proposals, and",
|
|
26510
|
+
"opens `req:write` issues for the downstream writer*; it does",
|
|
26511
|
+
"**not** write final requirement documents. Writing is the job of",
|
|
26512
|
+
"the downstream `requirements-writer` agent (a separate bundle).",
|
|
26513
|
+
"Keep that boundary clean: proposals land under the research",
|
|
26514
|
+
"requirements directory, not under the authoritative requirements",
|
|
26515
|
+
"tree. The draft-trace phase tags new issues with `req:write` so",
|
|
26516
|
+
"the writer bundle picks them up automatically.",
|
|
26064
26517
|
"",
|
|
26065
|
-
"See the `requirements-
|
|
26066
|
-
"details
|
|
26067
|
-
"phase-by-phase instructions."
|
|
26518
|
+
"See the `requirements-analyst` agent definition for full workflow",
|
|
26519
|
+
"details and phase-by-phase instructions."
|
|
26068
26520
|
].join("\n"),
|
|
26069
26521
|
platforms: {
|
|
26070
26522
|
cursor: { exclude: true }
|
|
@@ -26072,38 +26524,34 @@ function buildRequirementsWriterBundle(paths = DEFAULT_AGENT_PATHS, issueDefault
|
|
|
26072
26524
|
tags: ["workflow"]
|
|
26073
26525
|
}
|
|
26074
26526
|
],
|
|
26075
|
-
skills: [
|
|
26076
|
-
subAgents: [
|
|
26527
|
+
skills: [buildScanRequirementsSkill(issueDefaults)],
|
|
26528
|
+
subAgents: [buildRequirementsAnalystSubAgent(paths, issueDefaults)],
|
|
26529
|
+
// Shares the write-start ID allocator with the writer bundle. Both
|
|
26530
|
+
// bundles auto-detect and AgentConfig.resolveProcedures dedupes by
|
|
26531
|
+
// name, so `next-requirement-id.sh` is emitted to disk exactly once.
|
|
26532
|
+
// The analyst runs it (without a self-issue-number) to seed the first
|
|
26533
|
+
// reserved ID in a category during draft-trace.
|
|
26534
|
+
procedures: [nextRequirementIdProcedure],
|
|
26077
26535
|
labels: [
|
|
26078
26536
|
{
|
|
26079
|
-
name: "
|
|
26080
|
-
color: "
|
|
26081
|
-
description: "
|
|
26082
|
-
},
|
|
26083
|
-
{
|
|
26084
|
-
name: "tier:platform",
|
|
26085
|
-
color: "EDEDED",
|
|
26086
|
-
description: "Architectural tier: core platform (shared infrastructure, APIs, auth, tenant isolation)"
|
|
26087
|
-
},
|
|
26088
|
-
{
|
|
26089
|
-
name: "tier:industry",
|
|
26090
|
-
color: "EDEDED",
|
|
26091
|
-
description: "Architectural tier: industry vertical (capabilities not every tenant needs)"
|
|
26537
|
+
name: "type:requirement",
|
|
26538
|
+
color: "1D76DB",
|
|
26539
|
+
description: "Work that produces or discovers a requirement document (FR, BR, NFR, etc.)"
|
|
26092
26540
|
},
|
|
26093
26541
|
{
|
|
26094
|
-
name: "
|
|
26095
|
-
color: "
|
|
26096
|
-
description: "
|
|
26542
|
+
name: "req:scan",
|
|
26543
|
+
color: "C5DEF5",
|
|
26544
|
+
description: "Phase 1: scan source docs for requirement gaps and deduplicate"
|
|
26097
26545
|
},
|
|
26098
26546
|
{
|
|
26099
|
-
name: "
|
|
26100
|
-
color: "
|
|
26101
|
-
description: "
|
|
26547
|
+
name: "req:draft-trace",
|
|
26548
|
+
color: "BFDADC",
|
|
26549
|
+
description: "Phase 2: draft proposals, create req:write issues, and backfill source-doc traceability"
|
|
26102
26550
|
}
|
|
26103
26551
|
]
|
|
26104
26552
|
};
|
|
26105
26553
|
}
|
|
26106
|
-
var
|
|
26554
|
+
var requirementsAnalystBundle = buildRequirementsAnalystBundle();
|
|
26107
26555
|
|
|
26108
26556
|
// src/agent/bundles/requirements-reviewer.ts
|
|
26109
26557
|
function buildRequirementsReviewerSubAgent(paths, issueDefaults) {
|
|
@@ -30819,7 +31267,7 @@ var _TurboRepo = class _TurboRepo extends import_lib2.Component {
|
|
|
30819
31267
|
}
|
|
30820
31268
|
}
|
|
30821
31269
|
activateBranchNameEnvVar(options) {
|
|
30822
|
-
const value = '$(echo "$
|
|
31270
|
+
const value = '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git rev-parse --abbrev-ref HEAD)';
|
|
30823
31271
|
if (options === void 0) {
|
|
30824
31272
|
this.project.logger.warn(
|
|
30825
31273
|
"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."
|
|
@@ -31332,7 +31780,7 @@ var VERSION = {
|
|
|
31332
31780
|
/**
|
|
31333
31781
|
* Version of `@types/node` to use across all packages (pnpm catalog).
|
|
31334
31782
|
*/
|
|
31335
|
-
TYPES_NODE_VERSION: "26.1.
|
|
31783
|
+
TYPES_NODE_VERSION: "26.1.1",
|
|
31336
31784
|
/**
|
|
31337
31785
|
* What version of Vite to use (pnpm override). Pinned to 5.x so Vitest 4.x
|
|
31338
31786
|
* can load config (Vite 6+/7+ are ESM-only; see issue #142). Remove override
|
|
@@ -33024,7 +33472,11 @@ var DEFAULT_CLAUDE_HOOKS = {
|
|
|
33024
33472
|
hooks: [
|
|
33025
33473
|
{
|
|
33026
33474
|
type: "command",
|
|
33027
|
-
|
|
33475
|
+
// Claude Code delivers hook data as a JSON object on stdin, not
|
|
33476
|
+
// as CLAUDE_* env vars, so read the session id from `.session_id`
|
|
33477
|
+
// via jq. Token counts are not passed to hooks, so the two token
|
|
33478
|
+
// columns are dropped — usage.log is now `<timestamp>,<session-id>`.
|
|
33479
|
+
command: `sid="$(jq -r '.session_id // "unknown"' 2>/dev/null)"; echo "$(date -u +%FT%TZ),\${sid:-unknown}" >> ${DEFAULT_USAGE_LOG_PATH}`
|
|
33028
33480
|
}
|
|
33029
33481
|
]
|
|
33030
33482
|
}
|
|
@@ -33035,7 +33487,13 @@ var DEFAULT_CLAUDE_HOOKS = {
|
|
|
33035
33487
|
hooks: [
|
|
33036
33488
|
{
|
|
33037
33489
|
type: "command",
|
|
33038
|
-
|
|
33490
|
+
// Claude Code delivers hook data as a JSON object on stdin, not
|
|
33491
|
+
// as CLAUDE_* env vars, so read the edited file path from
|
|
33492
|
+
// `.tool_input.file_path` via jq. On files under
|
|
33493
|
+
// docs/src/content/docs/ this runs only the tool-artifact-tag
|
|
33494
|
+
// stripper (#779); it is `-x`-guarded so it no-ops cleanly when
|
|
33495
|
+
// the path is empty or the procedure script is not shipped.
|
|
33496
|
+
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`
|
|
33039
33497
|
}
|
|
33040
33498
|
]
|
|
33041
33499
|
}
|
|
@@ -33123,7 +33581,12 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
33123
33581
|
* entries. Both `allow` and `deny` are deduped via
|
|
33124
33582
|
* `Array.from(new Set(...))`; V8 `Set` iteration preserves insertion
|
|
33125
33583
|
* order, so the final ordering is defaults first, then bundle, then
|
|
33126
|
-
* user, with duplicates removed (first-occurrence wins).
|
|
33584
|
+
* user, with duplicates removed (first-occurrence wins). After the
|
|
33585
|
+
* merge+dedupe, any entry whose value exactly matches a string in
|
|
33586
|
+
* `permissions.excludeDefaults` is dropped from `allow` / `deny` / `ask`
|
|
33587
|
+
* — the only supported way to remove a baseline default, since the merge
|
|
33588
|
+
* is otherwise append-only. `excludeDefaults` is a build-time directive
|
|
33589
|
+
* and is stripped from the rendered `permissions` object. `defaultMode`
|
|
33127
33590
|
* is opt-in (steering decision D7): it is set only from
|
|
33128
33591
|
* `userSettings.defaultMode` and left undefined otherwise, so the
|
|
33129
33592
|
* renderer omits the key for un-opted-in consumers — see the inline
|
|
@@ -33148,6 +33611,29 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
33148
33611
|
const bundleDeny = bundlePermissions?.deny ?? [];
|
|
33149
33612
|
const userAllow = userSettings?.permissions?.allow ?? [];
|
|
33150
33613
|
const userDeny = userSettings?.permissions?.deny ?? [];
|
|
33614
|
+
const exclude = new Set(userSettings?.permissions?.excludeDefaults ?? []);
|
|
33615
|
+
const dropExcluded = (entries) => entries.filter((entry) => !exclude.has(entry));
|
|
33616
|
+
const { excludeDefaults: _excludeDefaults, ...passthroughPermissions } = userSettings?.permissions ?? {};
|
|
33617
|
+
const askOverride = passthroughPermissions.ask?.length ? { ask: dropExcluded([...passthroughPermissions.ask]) } : {};
|
|
33618
|
+
const mergedPermissions = {
|
|
33619
|
+
...passthroughPermissions,
|
|
33620
|
+
allow: dropExcluded(
|
|
33621
|
+
Array.from(
|
|
33622
|
+
/* @__PURE__ */ new Set([...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow])
|
|
33623
|
+
)
|
|
33624
|
+
),
|
|
33625
|
+
deny: dropExcluded(
|
|
33626
|
+
Array.from(
|
|
33627
|
+
/* @__PURE__ */ new Set([
|
|
33628
|
+
...DEFAULT_CLAUDE_DENY,
|
|
33629
|
+
...DEFAULT_CLAUDE_PATH_DENY,
|
|
33630
|
+
...bundleDeny,
|
|
33631
|
+
...userDeny
|
|
33632
|
+
])
|
|
33633
|
+
)
|
|
33634
|
+
),
|
|
33635
|
+
...askOverride
|
|
33636
|
+
};
|
|
33151
33637
|
return {
|
|
33152
33638
|
...userSettings,
|
|
33153
33639
|
// `defaultMode` is opt-in (steering decision D7): the base
|
|
@@ -33160,20 +33646,7 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
|
|
|
33160
33646
|
// confirmation prompts — opt in by setting
|
|
33161
33647
|
// `claudeSettings.defaultMode: "dontAsk"`.
|
|
33162
33648
|
defaultMode: userSettings?.defaultMode,
|
|
33163
|
-
permissions:
|
|
33164
|
-
...userSettings?.permissions,
|
|
33165
|
-
allow: Array.from(
|
|
33166
|
-
/* @__PURE__ */ new Set([...DEFAULT_CLAUDE_ALLOW, ...bundleAllow, ...userAllow])
|
|
33167
|
-
),
|
|
33168
|
-
deny: Array.from(
|
|
33169
|
-
/* @__PURE__ */ new Set([
|
|
33170
|
-
...DEFAULT_CLAUDE_DENY,
|
|
33171
|
-
...DEFAULT_CLAUDE_PATH_DENY,
|
|
33172
|
-
...bundleDeny,
|
|
33173
|
-
...userDeny
|
|
33174
|
-
])
|
|
33175
|
-
)
|
|
33176
|
-
},
|
|
33649
|
+
permissions: mergedPermissions,
|
|
33177
33650
|
hooks: _AgentConfig.mergeClaudeHooks(userSettings),
|
|
33178
33651
|
env: { ...DEFAULT_CLAUDE_ENV, ...userSettings?.env ?? {} }
|
|
33179
33652
|
};
|
|
@@ -35017,7 +35490,7 @@ var AwsDeploymentConfig = class _AwsDeploymentConfig extends import_projen12.Com
|
|
|
35017
35490
|
};
|
|
35018
35491
|
this.cdkCli = CdkCli.of(project) || new CdkCli(project);
|
|
35019
35492
|
this.env = {
|
|
35020
|
-
GIT_BRANCH_NAME: '$(echo "$
|
|
35493
|
+
GIT_BRANCH_NAME: '$([ -n "$GIT_BRANCH_NAME" ] && echo "$GIT_BRANCH_NAME" || git branch --show-current)'
|
|
35021
35494
|
};
|
|
35022
35495
|
this.projectPath = (0, import_node_path2.relative)(project.root.outdir, project.outdir);
|
|
35023
35496
|
this.rootPath = (0, import_node_path2.relative)(project.outdir, project.root.outdir);
|
|
@@ -36995,7 +37468,7 @@ export default preview;
|
|
|
36995
37468
|
}
|
|
36996
37469
|
|
|
36997
37470
|
// src/projects/astro-project.ts
|
|
36998
|
-
var
|
|
37471
|
+
var import_projen25 = require("projen");
|
|
36999
37472
|
var import_ts_deepmerge3 = require("ts-deepmerge");
|
|
37000
37473
|
|
|
37001
37474
|
// src/projects/monorepo-layout.ts
|
|
@@ -37143,7 +37616,7 @@ function resolveReactViteSiteProjectOutdir(packageName) {
|
|
|
37143
37616
|
}
|
|
37144
37617
|
|
|
37145
37618
|
// src/projects/typescript-project.ts
|
|
37146
|
-
var
|
|
37619
|
+
var import_projen24 = require("projen");
|
|
37147
37620
|
var import_javascript4 = require("projen/lib/javascript");
|
|
37148
37621
|
var import_release = require("projen/lib/release");
|
|
37149
37622
|
var import_ts_deepmerge2 = require("ts-deepmerge");
|
|
@@ -37587,8 +38060,62 @@ function patchStepArray3(steps, pinned) {
|
|
|
37587
38060
|
}
|
|
37588
38061
|
}
|
|
37589
38062
|
|
|
37590
|
-
// src/workflows/
|
|
38063
|
+
// src/workflows/requirement-issue-template.ts
|
|
37591
38064
|
var import_projen20 = require("projen");
|
|
38065
|
+
function renderRequirementBlock(fields) {
|
|
38066
|
+
return [
|
|
38067
|
+
"## Requirement",
|
|
38068
|
+
"",
|
|
38069
|
+
`- **ID:** ${fields.id}`,
|
|
38070
|
+
`- **Spec:** ${fields.specUrl}`,
|
|
38071
|
+
`- **Tier:** ${fields.tier}`,
|
|
38072
|
+
`- **Customer:** ${fields.customer}`
|
|
38073
|
+
].join("\n");
|
|
38074
|
+
}
|
|
38075
|
+
var DEFAULT_TEMPLATE_PATH = ".github/ISSUE_TEMPLATE/requirement-impl.md";
|
|
38076
|
+
var DEFAULT_TEMPLATE_LABELS = ["type:feat"];
|
|
38077
|
+
var RequirementIssueTemplate = class extends import_projen20.Component {
|
|
38078
|
+
constructor(project, options = {}) {
|
|
38079
|
+
super(project);
|
|
38080
|
+
const path8 = options.path ?? DEFAULT_TEMPLATE_PATH;
|
|
38081
|
+
const labels = options.labels ?? DEFAULT_TEMPLATE_LABELS;
|
|
38082
|
+
const labelsYaml = `[${labels.map((l) => `"${l}"`).join(", ")}]`;
|
|
38083
|
+
const requirementBlock = renderRequirementBlock({
|
|
38084
|
+
id: "<!-- e.g. FR-042 -->",
|
|
38085
|
+
specUrl: "<!-- URL to the canonical requirement doc in the planning repo -->",
|
|
38086
|
+
tier: "<!-- Platform | Industry | Customer Workflow | Consumer Application -->",
|
|
38087
|
+
customer: "<!-- customer scope, or \u2014 -->"
|
|
38088
|
+
});
|
|
38089
|
+
const lines = [
|
|
38090
|
+
"---",
|
|
38091
|
+
"name: Requirement implementation",
|
|
38092
|
+
"about: Implement a canonical requirement tracked in the planning repo",
|
|
38093
|
+
'title: "feat: implement <REQ-ID> \u2014 <short summary>"',
|
|
38094
|
+
`labels: ${labelsYaml}`,
|
|
38095
|
+
"---",
|
|
38096
|
+
"",
|
|
38097
|
+
...requirementBlock.split("\n"),
|
|
38098
|
+
"",
|
|
38099
|
+
"> Add the `req:<ID>` label (e.g. `req:FR-042`) so this issue is traceable to the requirement across repos.",
|
|
38100
|
+
"",
|
|
38101
|
+
"## Summary",
|
|
38102
|
+
"",
|
|
38103
|
+
"<!-- What must change in THIS repo to satisfy the requirement above. -->",
|
|
38104
|
+
"",
|
|
38105
|
+
"## Acceptance Criteria",
|
|
38106
|
+
"",
|
|
38107
|
+
"- [ ]"
|
|
38108
|
+
];
|
|
38109
|
+
new import_projen20.TextFile(project, path8, {
|
|
38110
|
+
marker: false,
|
|
38111
|
+
committed: true,
|
|
38112
|
+
lines
|
|
38113
|
+
});
|
|
38114
|
+
}
|
|
38115
|
+
};
|
|
38116
|
+
|
|
38117
|
+
// src/workflows/sync-labels.ts
|
|
38118
|
+
var import_projen21 = require("projen");
|
|
37592
38119
|
var import_workflows_model4 = require("projen/lib/github/workflows-model");
|
|
37593
38120
|
var DEFAULT_STATUS_LABELS = [
|
|
37594
38121
|
{
|
|
@@ -37796,10 +38323,10 @@ ${offenders}`
|
|
|
37796
38323
|
}
|
|
37797
38324
|
});
|
|
37798
38325
|
}
|
|
37799
|
-
var LabelsFile = class extends
|
|
38326
|
+
var LabelsFile = class extends import_projen21.Component {
|
|
37800
38327
|
constructor(project, labels) {
|
|
37801
38328
|
super(project);
|
|
37802
|
-
new
|
|
38329
|
+
new import_projen21.YamlFile(project, LABELS_CONFIG_PATH, {
|
|
37803
38330
|
obj: labels.map((l) => ({
|
|
37804
38331
|
name: l.name,
|
|
37805
38332
|
color: l.color,
|
|
@@ -38013,6 +38540,9 @@ var MonorepoProject = class extends import_typescript3.TypeScriptAppProject {
|
|
|
38013
38540
|
super({ ...options });
|
|
38014
38541
|
postInstallDependenciesMap.set(this, []);
|
|
38015
38542
|
this.tsconfig?.removeInclude(`${this.srcdir}/**/*.ts`);
|
|
38543
|
+
this.preCompileTask.exec(
|
|
38544
|
+
'[ -e "tsconfig.tsbuildinfo" ] && rm -f tsconfig.tsbuildinfo || true'
|
|
38545
|
+
);
|
|
38016
38546
|
this.pnpmVersion = options.pnpmVersion;
|
|
38017
38547
|
this.configulatorRegistryConsumer = options.configulatorRegistryConsumer ?? true;
|
|
38018
38548
|
this.layoutEnforcement = options.layoutEnforcement ?? LAYOUT_ENFORCEMENT.WARN;
|
|
@@ -38043,7 +38573,16 @@ var MonorepoProject = class extends import_typescript3.TypeScriptAppProject {
|
|
|
38043
38573
|
}
|
|
38044
38574
|
if (options.resetTask !== false) {
|
|
38045
38575
|
const defaultResetTaskOptions = {
|
|
38046
|
-
pathsToRemove: [
|
|
38576
|
+
pathsToRemove: [
|
|
38577
|
+
"node_modules",
|
|
38578
|
+
".turbo",
|
|
38579
|
+
"dist",
|
|
38580
|
+
"lib",
|
|
38581
|
+
// Root reset must also drop the incremental buildinfo (#656) so a
|
|
38582
|
+
// stale `.tsbuildinfo` can't hide a regressed dependency `.d.ts`
|
|
38583
|
+
// rollup from the next compile. Sub-projects already clean this.
|
|
38584
|
+
"tsconfig.tsbuildinfo"
|
|
38585
|
+
]
|
|
38047
38586
|
};
|
|
38048
38587
|
const userResetTaskOptions = options.resetTaskOptions ?? {};
|
|
38049
38588
|
const resetTaskOptions = (0, import_ts_deepmerge.merge)(
|
|
@@ -38087,6 +38626,12 @@ var MonorepoProject = class extends import_typescript3.TypeScriptAppProject {
|
|
|
38087
38626
|
bundles: syncLabelsOptions.bundles ?? agentConfig?.activeBundles
|
|
38088
38627
|
});
|
|
38089
38628
|
}
|
|
38629
|
+
if (options.requirementIssueTemplate) {
|
|
38630
|
+
new RequirementIssueTemplate(
|
|
38631
|
+
this,
|
|
38632
|
+
typeof options.requirementIssueTemplate === "object" ? options.requirementIssueTemplate : {}
|
|
38633
|
+
);
|
|
38634
|
+
}
|
|
38090
38635
|
if (this.buildWorkflow) {
|
|
38091
38636
|
addBuildCompleteJob(this.buildWorkflow);
|
|
38092
38637
|
}
|
|
@@ -38222,11 +38767,11 @@ var MonorepoProject = class extends import_typescript3.TypeScriptAppProject {
|
|
|
38222
38767
|
};
|
|
38223
38768
|
|
|
38224
38769
|
// src/typescript/tsdoc-config.ts
|
|
38225
|
-
var
|
|
38770
|
+
var import_projen22 = require("projen");
|
|
38226
38771
|
var STANDARD_MODIFIER_TAGS = ["@default"];
|
|
38227
38772
|
var STANDARD_INLINE_TAGS = ["@code"];
|
|
38228
38773
|
var ALWAYS_ON_SCOPES = ["@codedrifters"];
|
|
38229
|
-
var TsdocConfig = class _TsdocConfig extends
|
|
38774
|
+
var TsdocConfig = class _TsdocConfig extends import_projen22.Component {
|
|
38230
38775
|
/**
|
|
38231
38776
|
* Derive a workspace scope from a scoped package name (`@scope/name`).
|
|
38232
38777
|
* Returns `undefined` when the name is unscoped.
|
|
@@ -38271,7 +38816,7 @@ var TsdocConfig = class _TsdocConfig extends import_projen21.Component {
|
|
|
38271
38816
|
allowMultiple: true
|
|
38272
38817
|
}))
|
|
38273
38818
|
].sort((a, b) => a.tagName.localeCompare(b.tagName));
|
|
38274
|
-
new
|
|
38819
|
+
new import_projen22.JsonFile(project, "tsdoc.json", {
|
|
38275
38820
|
marker: false,
|
|
38276
38821
|
obj: {
|
|
38277
38822
|
$schema: "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
|
|
@@ -38284,9 +38829,9 @@ var TsdocConfig = class _TsdocConfig extends import_projen21.Component {
|
|
|
38284
38829
|
|
|
38285
38830
|
// src/typescript/typescript-config.ts
|
|
38286
38831
|
var import_node_path3 = require("path");
|
|
38287
|
-
var
|
|
38832
|
+
var import_projen23 = require("projen");
|
|
38288
38833
|
var import_path = require("projen/lib/util/path");
|
|
38289
|
-
var TypeScriptConfig = class extends
|
|
38834
|
+
var TypeScriptConfig = class extends import_projen23.Component {
|
|
38290
38835
|
constructor(project) {
|
|
38291
38836
|
super(project);
|
|
38292
38837
|
let tsPaths = {};
|
|
@@ -38317,7 +38862,7 @@ var TestRunner = {
|
|
|
38317
38862
|
JEST: "jest",
|
|
38318
38863
|
VITEST: "vitest"
|
|
38319
38864
|
};
|
|
38320
|
-
var TypeScriptProject = class extends
|
|
38865
|
+
var TypeScriptProject = class extends import_projen24.typescript.TypeScriptProject {
|
|
38321
38866
|
constructor(userOptions) {
|
|
38322
38867
|
if (!(userOptions.parent instanceof MonorepoProject)) {
|
|
38323
38868
|
throw new Error(
|
|
@@ -38647,10 +39192,10 @@ var AstroProject = class extends TypeScriptProject {
|
|
|
38647
39192
|
adapter: options.adapter
|
|
38648
39193
|
});
|
|
38649
39194
|
if (options.sampleCode === true) {
|
|
38650
|
-
new
|
|
39195
|
+
new import_projen25.SampleFile(this, "src/pages/index.astro", {
|
|
38651
39196
|
contents: DEFAULT_INDEX_ASTRO
|
|
38652
39197
|
});
|
|
38653
|
-
new
|
|
39198
|
+
new import_projen25.SampleFile(this, "public/favicon.svg", {
|
|
38654
39199
|
contents: DEFAULT_FAVICON_SVG
|
|
38655
39200
|
});
|
|
38656
39201
|
}
|
|
@@ -38675,19 +39220,19 @@ var DEFAULT_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
|
38675
39220
|
`;
|
|
38676
39221
|
|
|
38677
39222
|
// src/projects/aws-cdk-project.ts
|
|
38678
|
-
var
|
|
39223
|
+
var import_projen28 = require("projen");
|
|
38679
39224
|
var import_javascript5 = require("projen/lib/javascript");
|
|
38680
39225
|
var import_release2 = require("projen/lib/release");
|
|
38681
39226
|
var import_ts_deepmerge4 = require("ts-deepmerge");
|
|
38682
39227
|
|
|
38683
39228
|
// src/workflows/aws-deploy-workflow.ts
|
|
38684
39229
|
var import_utils11 = __toESM(require_lib());
|
|
38685
|
-
var
|
|
39230
|
+
var import_projen26 = require("projen");
|
|
38686
39231
|
var import_build = require("projen/lib/build");
|
|
38687
39232
|
var import_github6 = require("projen/lib/github");
|
|
38688
39233
|
var import_workflows_model5 = require("projen/lib/github/workflows-model");
|
|
38689
39234
|
var PROD_DEPLOY_NAME = "prod-deploy";
|
|
38690
|
-
var AwsDeployWorkflow = class _AwsDeployWorkflow extends
|
|
39235
|
+
var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Component {
|
|
38691
39236
|
constructor(project, options = {}) {
|
|
38692
39237
|
super(project);
|
|
38693
39238
|
this.project = project;
|
|
@@ -39004,7 +39549,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen25.Compone
|
|
|
39004
39549
|
};
|
|
39005
39550
|
|
|
39006
39551
|
// src/workflows/aws-teardown-workflow.ts
|
|
39007
|
-
var
|
|
39552
|
+
var import_projen27 = require("projen");
|
|
39008
39553
|
var import_github7 = require("projen/lib/github");
|
|
39009
39554
|
var import_workflows_model6 = require("projen/lib/github/workflows-model");
|
|
39010
39555
|
var DEFAULT_TEARDOWN_BRANCH_PATTERNS = [
|
|
@@ -39026,7 +39571,7 @@ var resolveBranchPatterns = (explicit, targets) => {
|
|
|
39026
39571
|
}
|
|
39027
39572
|
return [...DEFAULT_TEARDOWN_BRANCH_PATTERNS];
|
|
39028
39573
|
};
|
|
39029
|
-
var AwsTeardownWorkflow = class extends
|
|
39574
|
+
var AwsTeardownWorkflow = class extends import_projen27.Component {
|
|
39030
39575
|
constructor(rootProject, options) {
|
|
39031
39576
|
super(rootProject);
|
|
39032
39577
|
this.rootProject = rootProject;
|
|
@@ -39208,7 +39753,7 @@ var AwsTeardownWorkflow = class extends import_projen26.Component {
|
|
|
39208
39753
|
};
|
|
39209
39754
|
|
|
39210
39755
|
// src/projects/aws-cdk-project.ts
|
|
39211
|
-
var AwsCdkProject = class extends
|
|
39756
|
+
var AwsCdkProject = class extends import_projen28.awscdk.AwsCdkTypeScriptApp {
|
|
39212
39757
|
constructor(userOptions) {
|
|
39213
39758
|
if (!(userOptions.parent instanceof MonorepoProject)) {
|
|
39214
39759
|
throw new Error(
|
|
@@ -39418,7 +39963,7 @@ var AwsCdkProject = class extends import_projen27.awscdk.AwsCdkTypeScriptApp {
|
|
|
39418
39963
|
};
|
|
39419
39964
|
|
|
39420
39965
|
// src/projects/react-vite-site-project.ts
|
|
39421
|
-
var
|
|
39966
|
+
var import_projen29 = require("projen");
|
|
39422
39967
|
var import_ts_deepmerge5 = require("ts-deepmerge");
|
|
39423
39968
|
var ReactViteSiteProject = class extends TypeScriptProject {
|
|
39424
39969
|
constructor(userOptions) {
|
|
@@ -39457,7 +40002,7 @@ var ReactViteSiteProject = class extends TypeScriptProject {
|
|
|
39457
40002
|
};
|
|
39458
40003
|
super(options);
|
|
39459
40004
|
this.package.addField("type", "module");
|
|
39460
|
-
new
|
|
40005
|
+
new import_projen29.TextFile(this, ".nvmrc", { lines: ["v24.11.0"] });
|
|
39461
40006
|
this.tsconfig?.file.addOverride("compilerOptions.target", "ES2020");
|
|
39462
40007
|
this.tsconfig?.file.addOverride("compilerOptions.lib", [
|
|
39463
40008
|
"ES2020",
|
|
@@ -39492,7 +40037,7 @@ var ReactViteSiteProject = class extends TypeScriptProject {
|
|
|
39492
40037
|
"vitest/globals",
|
|
39493
40038
|
"vite/client"
|
|
39494
40039
|
]);
|
|
39495
|
-
new
|
|
40040
|
+
new import_projen29.SampleFile(this, "vite.config.ts", {
|
|
39496
40041
|
contents: `import { defineConfig } from 'vite';
|
|
39497
40042
|
import react from '@vitejs/plugin-react';
|
|
39498
40043
|
import tailwindcss from '@tailwindcss/vite';
|
|
@@ -39504,7 +40049,7 @@ export default defineConfig({
|
|
|
39504
40049
|
});
|
|
39505
40050
|
`
|
|
39506
40051
|
});
|
|
39507
|
-
new
|
|
40052
|
+
new import_projen29.SampleFile(this, "src/vite-env.d.ts", {
|
|
39508
40053
|
contents: `/// <reference types="vite/client" />
|
|
39509
40054
|
|
|
39510
40055
|
interface ImportMetaEnv {
|
|
@@ -39519,7 +40064,7 @@ interface ImportMeta {
|
|
|
39519
40064
|
});
|
|
39520
40065
|
if (options.testRunner !== TestRunner.JEST) {
|
|
39521
40066
|
this.tryRemoveFile("vitest.config.ts");
|
|
39522
|
-
new
|
|
40067
|
+
new import_projen29.SampleFile(this, "vitest.config.ts", {
|
|
39523
40068
|
contents: `import { defineConfig, mergeConfig } from 'vitest/config';
|
|
39524
40069
|
import react from '@vitejs/plugin-react';
|
|
39525
40070
|
import viteConfig from './vite.config';
|
|
@@ -39663,7 +40208,7 @@ export default mergeConfig(
|
|
|
39663
40208
|
}
|
|
39664
40209
|
if (userOptions.sampleCode === true) {
|
|
39665
40210
|
const siteName = options.name;
|
|
39666
|
-
new
|
|
40211
|
+
new import_projen29.SampleFile(this, "index.html", {
|
|
39667
40212
|
contents: `<!doctype html>
|
|
39668
40213
|
<html>
|
|
39669
40214
|
<head>
|
|
@@ -39678,7 +40223,7 @@ export default mergeConfig(
|
|
|
39678
40223
|
</html>
|
|
39679
40224
|
`
|
|
39680
40225
|
});
|
|
39681
|
-
new
|
|
40226
|
+
new import_projen29.SampleFile(this, "src/routes.tsx", {
|
|
39682
40227
|
contents: `import { createBrowserRouter } from 'react-router-dom';
|
|
39683
40228
|
import App from './App';
|
|
39684
40229
|
|
|
@@ -39687,7 +40232,7 @@ export const router = createBrowserRouter([
|
|
|
39687
40232
|
]);
|
|
39688
40233
|
`
|
|
39689
40234
|
});
|
|
39690
|
-
new
|
|
40235
|
+
new import_projen29.SampleFile(this, "src/main.tsx", {
|
|
39691
40236
|
contents: `import React from 'react';
|
|
39692
40237
|
import ReactDOM from 'react-dom/client';
|
|
39693
40238
|
import { RouterProvider } from 'react-router-dom';
|
|
@@ -39701,7 +40246,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
39701
40246
|
);
|
|
39702
40247
|
`
|
|
39703
40248
|
});
|
|
39704
|
-
new
|
|
40249
|
+
new import_projen29.SampleFile(this, "src/App.tsx", {
|
|
39705
40250
|
contents: `export default function App() {
|
|
39706
40251
|
return (
|
|
39707
40252
|
<main className="p-4">
|
|
@@ -39711,11 +40256,11 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
39711
40256
|
}
|
|
39712
40257
|
`
|
|
39713
40258
|
});
|
|
39714
|
-
new
|
|
40259
|
+
new import_projen29.SampleFile(this, "src/index.css", {
|
|
39715
40260
|
contents: `@import "tailwindcss";
|
|
39716
40261
|
`
|
|
39717
40262
|
});
|
|
39718
|
-
new
|
|
40263
|
+
new import_projen29.SampleFile(this, "src/setupTests.ts", {
|
|
39719
40264
|
contents: `import '@testing-library/jest-dom';
|
|
39720
40265
|
`
|
|
39721
40266
|
});
|
|
@@ -39724,7 +40269,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
39724
40269
|
};
|
|
39725
40270
|
|
|
39726
40271
|
// src/projects/starlight-project.ts
|
|
39727
|
-
var
|
|
40272
|
+
var import_projen30 = require("projen");
|
|
39728
40273
|
var STARLIGHT_ROLE = {
|
|
39729
40274
|
DOCS: "docs",
|
|
39730
40275
|
SITE: "site"
|
|
@@ -39766,10 +40311,10 @@ var StarlightProject = class extends AstroProject {
|
|
|
39766
40311
|
turbo.compileTask.inputs.push("src/content/**");
|
|
39767
40312
|
}
|
|
39768
40313
|
if (userOptions.sampleContent === true) {
|
|
39769
|
-
new
|
|
40314
|
+
new import_projen30.SampleFile(this, "src/content/docs/index.mdx", {
|
|
39770
40315
|
contents: DEFAULT_INDEX_MDX
|
|
39771
40316
|
});
|
|
39772
|
-
new
|
|
40317
|
+
new import_projen30.SampleFile(this, "src/content.config.ts", {
|
|
39773
40318
|
contents: DEFAULT_CONTENT_CONFIG_TS
|
|
39774
40319
|
});
|
|
39775
40320
|
}
|
|
@@ -39959,6 +40504,7 @@ export const collections = {
|
|
|
39959
40504
|
ROOT_CI_TASK_NAME,
|
|
39960
40505
|
ROOT_TURBO_TASK_NAME,
|
|
39961
40506
|
ReactViteSiteProject,
|
|
40507
|
+
RequirementIssueTemplate,
|
|
39962
40508
|
ResetTask,
|
|
39963
40509
|
SCHEDULED_TASK_MODEL_VALUES,
|
|
39964
40510
|
SCOPE_CLASS_VALUES,
|
|
@@ -40059,6 +40605,7 @@ export const collections = {
|
|
|
40059
40605
|
maintenanceAuditBundle,
|
|
40060
40606
|
meetingAnalysisBundle,
|
|
40061
40607
|
mergeCdkOptions,
|
|
40608
|
+
nextRequirementIdProcedure,
|
|
40062
40609
|
orchestratorBundle,
|
|
40063
40610
|
parseApiRollup,
|
|
40064
40611
|
peopleProfileBundle,
|
|
@@ -40106,11 +40653,13 @@ export const collections = {
|
|
|
40106
40653
|
renderIssueTemplatesRuleContent,
|
|
40107
40654
|
renderIssueTemplatesStarterPage,
|
|
40108
40655
|
renderMeetingTypesSection,
|
|
40656
|
+
renderNextRequirementIdProcedure,
|
|
40109
40657
|
renderPriorityRulesSection,
|
|
40110
40658
|
renderProgressFileName,
|
|
40111
40659
|
renderProgressFilePath,
|
|
40112
40660
|
renderProgressFilesBundleHook,
|
|
40113
40661
|
renderProgressFilesRuleContent,
|
|
40662
|
+
renderRequirementBlock,
|
|
40114
40663
|
renderRunRatioSection,
|
|
40115
40664
|
renderRunRatioShellHelpers,
|
|
40116
40665
|
renderScheduledTaskSkillFile,
|