@event4u/agent-config 1.31.0 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/.agent-src/skills/feature-planning/SKILL.md +43 -7
  2. package/.agent-src/skills/judge-test-coverage/SKILL.md +4 -0
  3. package/.agent-src/skills/pest-testing/SKILL.md +13 -6
  4. package/.agent-src/skills/quality-tools/SKILL.md +4 -0
  5. package/.agent-src/skills/refine-prompt/SKILL.md +10 -0
  6. package/.agent-src/skills/refine-ticket/SKILL.md +12 -0
  7. package/.agent-src/skills/subagent-orchestration/SKILL.md +77 -12
  8. package/.agent-src/skills/subagent-orchestration/prompts/README.md +29 -0
  9. package/.agent-src/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md +121 -0
  10. package/.agent-src/skills/subagent-orchestration/prompts/do-and-judge.md +60 -0
  11. package/.agent-src/skills/subagent-orchestration/prompts/do-competitively.md +65 -0
  12. package/.agent-src/skills/subagent-orchestration/prompts/do-in-parallel.md +62 -0
  13. package/.agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +62 -0
  14. package/.agent-src/skills/subagent-orchestration/prompts/do-in-worktrees.md +70 -0
  15. package/.agent-src/skills/subagent-orchestration/prompts/judge-with-debate.md +63 -0
  16. package/.agent-src/skills/subagent-orchestration/schemas/subagent-status.json +63 -0
  17. package/.agent-src/skills/test-driven-development/SKILL.md +25 -13
  18. package/.agent-src/skills/testing-anti-patterns/SKILL.md +7 -0
  19. package/.agent-src/skills/testing-anti-patterns/process-anti-patterns.md +67 -0
  20. package/.claude-plugin/marketplace.json +1 -1
  21. package/CHANGELOG.md +24 -0
  22. package/docs/catalog.md +1 -1
  23. package/docs/contracts/file-ownership-matrix.json +341 -0
  24. package/package.json +1 -1
  25. package/scripts/check_bite_sized_granularity.py +99 -0
@@ -2046,6 +2046,54 @@
2046
2046
  "load_context": [],
2047
2047
  "load_context_eager": []
2048
2048
  },
2049
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md": {
2050
+ "kind": "skill",
2051
+ "rule_type": null,
2052
+ "load_context": [],
2053
+ "load_context_eager": []
2054
+ },
2055
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md": {
2056
+ "kind": "skill",
2057
+ "rule_type": null,
2058
+ "load_context": [],
2059
+ "load_context_eager": []
2060
+ },
2061
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge.md": {
2062
+ "kind": "skill",
2063
+ "rule_type": null,
2064
+ "load_context": [],
2065
+ "load_context_eager": []
2066
+ },
2067
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-competitively.md": {
2068
+ "kind": "skill",
2069
+ "rule_type": null,
2070
+ "load_context": [],
2071
+ "load_context_eager": []
2072
+ },
2073
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-parallel.md": {
2074
+ "kind": "skill",
2075
+ "rule_type": null,
2076
+ "load_context": [],
2077
+ "load_context_eager": []
2078
+ },
2079
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-steps.md": {
2080
+ "kind": "skill",
2081
+ "rule_type": null,
2082
+ "load_context": [],
2083
+ "load_context_eager": []
2084
+ },
2085
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md": {
2086
+ "kind": "skill",
2087
+ "rule_type": null,
2088
+ "load_context": [],
2089
+ "load_context_eager": []
2090
+ },
2091
+ ".agent-src.uncompressed/skills/subagent-orchestration/prompts/judge-with-debate.md": {
2092
+ "kind": "skill",
2093
+ "rule_type": null,
2094
+ "load_context": [],
2095
+ "load_context_eager": []
2096
+ },
2049
2097
  ".agent-src.uncompressed/skills/systematic-debugging/SKILL.md": {
2050
2098
  "kind": "skill",
2051
2099
  "rule_type": null,
@@ -2088,6 +2136,12 @@
2088
2136
  "load_context": [],
2089
2137
  "load_context_eager": []
2090
2138
  },
2139
+ ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md": {
2140
+ "kind": "skill",
2141
+ "rule_type": null,
2142
+ "load_context": [],
2143
+ "load_context_eager": []
2144
+ },
2091
2145
  ".agent-src.uncompressed/skills/threat-modeling/SKILL.md": {
2092
2146
  "kind": "skill",
2093
2147
  "rule_type": null,
@@ -6840,6 +6894,20 @@
6840
6894
  "via": "body_link",
6841
6895
  "depth": 1
6842
6896
  },
6897
+ {
6898
+ "source": ".agent-src.uncompressed/skills/judge-test-coverage/SKILL.md",
6899
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
6900
+ "type": "READ_ONLY",
6901
+ "via": "body_link",
6902
+ "depth": 1
6903
+ },
6904
+ {
6905
+ "source": ".agent-src.uncompressed/skills/judge-test-coverage/SKILL.md",
6906
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
6907
+ "type": "READ_ONLY",
6908
+ "via": "body_link",
6909
+ "depth": 1
6910
+ },
6843
6911
  {
6844
6912
  "source": ".agent-src.uncompressed/skills/laravel-horizon/SKILL.md",
6845
6913
  "target": ".agent-src.uncompressed/skills/jobs-events/SKILL.md",
@@ -7197,6 +7265,27 @@
7197
7265
  "via": "self",
7198
7266
  "depth": 0
7199
7267
  },
7268
+ {
7269
+ "source": ".agent-src.uncompressed/skills/pest-testing/SKILL.md",
7270
+ "target": ".agent-src.uncompressed/skills/test-driven-development/SKILL.md",
7271
+ "type": "READ_ONLY",
7272
+ "via": "body_link",
7273
+ "depth": 1
7274
+ },
7275
+ {
7276
+ "source": ".agent-src.uncompressed/skills/pest-testing/SKILL.md",
7277
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
7278
+ "type": "READ_ONLY",
7279
+ "via": "body_link",
7280
+ "depth": 1
7281
+ },
7282
+ {
7283
+ "source": ".agent-src.uncompressed/skills/pest-testing/SKILL.md",
7284
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
7285
+ "type": "READ_ONLY",
7286
+ "via": "body_link",
7287
+ "depth": 1
7288
+ },
7200
7289
  {
7201
7290
  "source": ".agent-src.uncompressed/skills/php-coder/SKILL.md",
7202
7291
  "target": ".agent-src.uncompressed/skills/php-coder/SKILL.md",
@@ -7372,6 +7461,20 @@
7372
7461
  "via": "self",
7373
7462
  "depth": 0
7374
7463
  },
7464
+ {
7465
+ "source": ".agent-src.uncompressed/skills/quality-tools/SKILL.md",
7466
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
7467
+ "type": "READ_ONLY",
7468
+ "via": "body_link",
7469
+ "depth": 1
7470
+ },
7471
+ {
7472
+ "source": ".agent-src.uncompressed/skills/quality-tools/SKILL.md",
7473
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
7474
+ "type": "READ_ONLY",
7475
+ "via": "body_link",
7476
+ "depth": 1
7477
+ },
7375
7478
  {
7376
7479
  "source": ".agent-src.uncompressed/skills/react-native-setup/SKILL.md",
7377
7480
  "target": ".agent-src.uncompressed/skills/react-native-setup/SKILL.md",
@@ -8060,11 +8163,186 @@
8060
8163
  },
8061
8164
  {
8062
8165
  "source": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8166
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8167
+ "type": "READ_ONLY",
8168
+ "via": "body_link",
8169
+ "depth": 1
8170
+ },
8171
+ {
8172
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8173
+ "target": ".agent-src.uncompressed/skills/using-git-worktrees/SKILL.md",
8174
+ "type": "READ_ONLY",
8175
+ "via": "body_link",
8176
+ "depth": 1
8177
+ },
8178
+ {
8179
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8180
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8181
+ "type": "READ_ONLY",
8182
+ "via": "body_link",
8183
+ "depth": 1
8184
+ },
8185
+ {
8186
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8187
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8188
+ "type": "WRITE",
8189
+ "via": "self",
8190
+ "depth": 0
8191
+ },
8192
+ {
8193
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8194
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md",
8195
+ "type": "READ_ONLY",
8196
+ "via": "body_link",
8197
+ "depth": 1
8198
+ },
8199
+ {
8200
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8201
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge.md",
8202
+ "type": "READ_ONLY",
8203
+ "via": "body_link",
8204
+ "depth": 1
8205
+ },
8206
+ {
8207
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8208
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-competitively.md",
8209
+ "type": "READ_ONLY",
8210
+ "via": "body_link",
8211
+ "depth": 1
8212
+ },
8213
+ {
8214
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8215
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-parallel.md",
8216
+ "type": "READ_ONLY",
8217
+ "via": "body_link",
8218
+ "depth": 1
8219
+ },
8220
+ {
8221
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8222
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-steps.md",
8223
+ "type": "READ_ONLY",
8224
+ "via": "body_link",
8225
+ "depth": 1
8226
+ },
8227
+ {
8228
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8229
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md",
8230
+ "type": "READ_ONLY",
8231
+ "via": "body_link",
8232
+ "depth": 1
8233
+ },
8234
+ {
8235
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/README.md",
8236
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/judge-with-debate.md",
8237
+ "type": "READ_ONLY",
8238
+ "via": "body_link",
8239
+ "depth": 1
8240
+ },
8241
+ {
8242
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md",
8243
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8244
+ "type": "READ_ONLY",
8245
+ "via": "body_link",
8246
+ "depth": 1
8247
+ },
8248
+ {
8249
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md",
8250
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge-two-stage.md",
8251
+ "type": "WRITE",
8252
+ "via": "self",
8253
+ "depth": 0
8254
+ },
8255
+ {
8256
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge.md",
8257
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8258
+ "type": "READ_ONLY",
8259
+ "via": "body_link",
8260
+ "depth": 1
8261
+ },
8262
+ {
8263
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge.md",
8264
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-and-judge.md",
8265
+ "type": "WRITE",
8266
+ "via": "self",
8267
+ "depth": 0
8268
+ },
8269
+ {
8270
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-competitively.md",
8271
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8272
+ "type": "READ_ONLY",
8273
+ "via": "body_link",
8274
+ "depth": 1
8275
+ },
8276
+ {
8277
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-competitively.md",
8278
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-competitively.md",
8279
+ "type": "WRITE",
8280
+ "via": "self",
8281
+ "depth": 0
8282
+ },
8283
+ {
8284
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-parallel.md",
8285
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8286
+ "type": "READ_ONLY",
8287
+ "via": "body_link",
8288
+ "depth": 1
8289
+ },
8290
+ {
8291
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-parallel.md",
8292
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-parallel.md",
8293
+ "type": "WRITE",
8294
+ "via": "self",
8295
+ "depth": 0
8296
+ },
8297
+ {
8298
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-steps.md",
8299
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8300
+ "type": "READ_ONLY",
8301
+ "via": "body_link",
8302
+ "depth": 1
8303
+ },
8304
+ {
8305
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-steps.md",
8306
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-steps.md",
8307
+ "type": "WRITE",
8308
+ "via": "self",
8309
+ "depth": 0
8310
+ },
8311
+ {
8312
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md",
8313
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8314
+ "type": "READ_ONLY",
8315
+ "via": "body_link",
8316
+ "depth": 1
8317
+ },
8318
+ {
8319
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md",
8320
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md",
8321
+ "type": "WRITE",
8322
+ "via": "self",
8323
+ "depth": 0
8324
+ },
8325
+ {
8326
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/do-in-worktrees.md",
8063
8327
  "target": ".agent-src.uncompressed/skills/using-git-worktrees/SKILL.md",
8064
8328
  "type": "READ_ONLY",
8065
8329
  "via": "body_link",
8066
8330
  "depth": 1
8067
8331
  },
8332
+ {
8333
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/judge-with-debate.md",
8334
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/SKILL.md",
8335
+ "type": "READ_ONLY",
8336
+ "via": "body_link",
8337
+ "depth": 1
8338
+ },
8339
+ {
8340
+ "source": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/judge-with-debate.md",
8341
+ "target": ".agent-src.uncompressed/skills/subagent-orchestration/prompts/judge-with-debate.md",
8342
+ "type": "WRITE",
8343
+ "via": "self",
8344
+ "depth": 0
8345
+ },
8068
8346
  {
8069
8347
  "source": ".agent-src.uncompressed/skills/systematic-debugging/SKILL.md",
8070
8348
  "target": ".agent-src.uncompressed/skills/blast-radius-analyzer/SKILL.md",
@@ -8177,6 +8455,20 @@
8177
8455
  "via": "self",
8178
8456
  "depth": 0
8179
8457
  },
8458
+ {
8459
+ "source": ".agent-src.uncompressed/skills/test-driven-development/SKILL.md",
8460
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
8461
+ "type": "READ_ONLY",
8462
+ "via": "body_link",
8463
+ "depth": 1
8464
+ },
8465
+ {
8466
+ "source": ".agent-src.uncompressed/skills/test-driven-development/SKILL.md",
8467
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8468
+ "type": "READ_ONLY",
8469
+ "via": "body_link",
8470
+ "depth": 1
8471
+ },
8180
8472
  {
8181
8473
  "source": ".agent-src.uncompressed/skills/test-performance/SKILL.md",
8182
8474
  "target": ".agent-src.uncompressed/skills/test-performance/SKILL.md",
@@ -8226,6 +8518,55 @@
8226
8518
  "via": "self",
8227
8519
  "depth": 0
8228
8520
  },
8521
+ {
8522
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
8523
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8524
+ "type": "READ_ONLY",
8525
+ "via": "body_link",
8526
+ "depth": 1
8527
+ },
8528
+ {
8529
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8530
+ "target": ".agent-src.uncompressed/skills/judge-test-coverage/SKILL.md",
8531
+ "type": "READ_ONLY",
8532
+ "via": "body_link",
8533
+ "depth": 1
8534
+ },
8535
+ {
8536
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8537
+ "target": ".agent-src.uncompressed/skills/pest-testing/SKILL.md",
8538
+ "type": "READ_ONLY",
8539
+ "via": "body_link",
8540
+ "depth": 1
8541
+ },
8542
+ {
8543
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8544
+ "target": ".agent-src.uncompressed/skills/quality-tools/SKILL.md",
8545
+ "type": "READ_ONLY",
8546
+ "via": "body_link",
8547
+ "depth": 1
8548
+ },
8549
+ {
8550
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8551
+ "target": ".agent-src.uncompressed/skills/test-driven-development/SKILL.md",
8552
+ "type": "READ_ONLY",
8553
+ "via": "body_link",
8554
+ "depth": 1
8555
+ },
8556
+ {
8557
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8558
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/SKILL.md",
8559
+ "type": "READ_ONLY",
8560
+ "via": "body_link",
8561
+ "depth": 1
8562
+ },
8563
+ {
8564
+ "source": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8565
+ "target": ".agent-src.uncompressed/skills/testing-anti-patterns/process-anti-patterns.md",
8566
+ "type": "WRITE",
8567
+ "via": "self",
8568
+ "depth": 0
8569
+ },
8229
8570
  {
8230
8571
  "source": ".agent-src.uncompressed/skills/threat-modeling/SKILL.md",
8231
8572
  "target": ".agent-src.uncompressed/skills/authz-review/SKILL.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Shared agent configuration \u2014 skills, rules, commands, guidelines, and templates for AI coding tools",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """Bite-sized task granularity gate for structural roadmaps (P1.5).
3
+
4
+ Adopted from `obra/superpowers` `writing-plans/SKILL.md` § Task Structure +
5
+ § No Placeholders (v5.1.0). Complexity-gating is our addition (Council
6
+ Round 1, Q4) — only roadmaps tagged `complexity: structural` in frontmatter
7
+ are subject to the granularity rules; `complexity: lightweight` skips.
8
+
9
+ Public API (stdlib-only):
10
+
11
+ read_complexity(text) -> 'structural' | 'lightweight' | None
12
+ scan_placeholders(text) -> list[Placeholder]
13
+ check_granularity(text) -> Result(complexity, gated, violations)
14
+
15
+ `gated` is True only when `complexity == 'structural'`. Violations are
16
+ empty when the gate is not active, regardless of placeholder presence.
17
+
18
+ The CI contract for P1.5 is the pytest harness in
19
+ `tests/test_bite_sized_granularity.py`; this module is the test surface.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from dataclasses import dataclass, field
25
+
26
+ PLACEHOLDER_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
27
+ ("angle-placeholder", re.compile(r"<[a-z][a-z0-9 _\-/]*>", re.IGNORECASE)),
28
+ ("todo", re.compile(r"\bTODO\b")),
29
+ ("fixme", re.compile(r"\bFIXME\b")),
30
+ ("xxx", re.compile(r"\bXXX\b")),
31
+ ("tbd", re.compile(r"\btbd\b", re.IGNORECASE)),
32
+ ("triple-question", re.compile(r"\?\?\?")),
33
+ )
34
+
35
+ COMPLEXITY_PAT = re.compile(
36
+ r"^complexity:\s*(lightweight|structural)\s*$", re.MULTILINE
37
+ )
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class Placeholder:
42
+ kind: str
43
+ line: int
44
+ text: str
45
+
46
+
47
+ @dataclass
48
+ class Result:
49
+ complexity: str | None
50
+ gated: bool
51
+ violations: list[Placeholder] = field(default_factory=list)
52
+
53
+
54
+ def _frontmatter(text: str) -> str:
55
+ if not text.startswith("---\n"):
56
+ return ""
57
+ end = text.find("\n---\n", 4)
58
+ return text[4:end] if end != -1 else ""
59
+
60
+
61
+ def read_complexity(text: str) -> str | None:
62
+ """Return the `complexity:` value from the roadmap frontmatter, or None."""
63
+ fm = _frontmatter(text)
64
+ if not fm:
65
+ return None
66
+ m = COMPLEXITY_PAT.search(fm)
67
+ return m.group(1) if m else None
68
+
69
+
70
+ def scan_placeholders(text: str) -> list[Placeholder]:
71
+ """Return every placeholder hit in task-bullet lines (`- [ ]` / `- [x]`)."""
72
+ hits: list[Placeholder] = []
73
+ for line_no, line in enumerate(text.splitlines(), start=1):
74
+ stripped = line.lstrip()
75
+ if not stripped.startswith(("- [ ]", "- [x]", "- [/]", "- [-]")):
76
+ continue
77
+ for kind, pat in PLACEHOLDER_PATTERNS:
78
+ if pat.search(line):
79
+ hits.append(Placeholder(kind=kind, line=line_no, text=line.rstrip()))
80
+ break
81
+ return hits
82
+
83
+
84
+ def check_granularity(text: str) -> Result:
85
+ """Run the granularity gate.
86
+
87
+ Structural roadmaps fail on any placeholder hit in task bullets.
88
+ Lightweight or untagged roadmaps skip the gate (gated=False) and
89
+ return an empty violation list even when placeholders are present.
90
+ """
91
+ complexity = read_complexity(text)
92
+ gated = complexity == "structural"
93
+ if not gated:
94
+ return Result(complexity=complexity, gated=False, violations=[])
95
+ return Result(
96
+ complexity=complexity,
97
+ gated=True,
98
+ violations=scan_placeholders(text),
99
+ )