@antoneeo/agentic-sdlc-skill 1.28.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +4 -0
- package/gemini-extension.json +6 -6
- package/package.json +50 -50
- package/scripts/init.js +22 -11
- package/scripts/lib.js +232 -1
- package/scripts/postinstall.js +34 -2
- package/scripts/preuninstall.js +14 -1
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +31 -0
- package/skills/agentic-sdlc-skill/SKILL.md +354 -352
- package/skills/agentic-sdlc-skill/review.md +51 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +138 -0
|
@@ -94,6 +94,23 @@ Hybrid project's devPNT gates write to the same file.
|
|
|
94
94
|
The log is how the gate's value is measured over time; skipping it makes the gate
|
|
95
95
|
unfalsifiable, the same defect as an unnamed EXISTS or a faked router verdict.
|
|
96
96
|
|
|
97
|
+
**Revise means converge, not accrete.** Two revision failure modes generate their
|
|
98
|
+
own findings and can make the cap unreachable honestly. (1) *Patch-on-patch*:
|
|
99
|
+
each round's fixes are grafted onto the previous text until stale counts,
|
|
100
|
+
duplicated sections and internal contradictions are findings the revisions
|
|
101
|
+
themselves created. When a round's findings are of that artifact-consistency
|
|
102
|
+
class — and at the latest before a round-3 attempt — rewrite the artifact lean
|
|
103
|
+
and operative against the checklist instead of patching again; a from-scratch
|
|
104
|
+
rewrite is cheaper than a burned round. (2) *Revision narrative in the
|
|
105
|
+
artifact*: the artifact under review carries its own review archaeology
|
|
106
|
+
("v1.2 fixed…", "as the round-2 review noted…"). An artifact states what IS,
|
|
107
|
+
once; how it got there lives in the REVIEW_LOG row's notes, never in the
|
|
108
|
+
artifact — archaeology bloats the object under review and every stale
|
|
109
|
+
self-reference is a future finding. Field measurement (2026-08): an artifact
|
|
110
|
+
that had grown to ~1,250 lines across eight failing rounds passed on the round
|
|
111
|
+
after a ~500-line operative rewrite moved the history out — the rewrite removed
|
|
112
|
+
an entire finding class.
|
|
113
|
+
|
|
97
114
|
**The reviewer is read-only and advisory.** It never edits, never commits, never
|
|
98
115
|
marks anything DONE, and a PASS is not an approval to merge — the human owns that.
|
|
99
116
|
|
|
@@ -124,6 +141,26 @@ When you hand work to a reviewer (human or agent), give them:
|
|
|
124
141
|
should have been. The design reviewer checks that every threat surface the change
|
|
125
142
|
touches has a matching security requirement.
|
|
126
143
|
|
|
144
|
+
- **The severity contract**: state with the request what counts as a blocker
|
|
145
|
+
versus a lesser finding. A blocker is *unfit-to-proceed* evidence only — a
|
|
146
|
+
false claim about existing code, a published falsehood, a missing impacted
|
|
147
|
+
file, an unresolvable internal contradiction, a real regression; an
|
|
148
|
+
improvement idea, a wish, or a style preference is at most a WARN and never
|
|
149
|
+
fails the artifact. An uncalibrated "hunt hard" mandate produces
|
|
150
|
+
blocker-inflated rounds that fail artifacts on wishes — each one burns a
|
|
151
|
+
round from the cap of 3 and patches the artifact for prose, the accretion
|
|
152
|
+
failure the round-cap rule names. Stating the taxonomy calibrates the
|
|
153
|
+
*scale*; it never pre-judges any specific finding (that stays forbidden
|
|
154
|
+
below) — the reviewer still decides where every finding lands.
|
|
155
|
+
|
|
156
|
+
**Pre-audit before you request.** Before handing the artifact over, run a
|
|
157
|
+
scripted author pre-audit: mechanically resolve every `file:line` / symbol
|
|
158
|
+
anchor the artifact cites against the real tree, and recount every count it
|
|
159
|
+
states — with a script, not by eye. Anchors rot between drafts, and a reviewer
|
|
160
|
+
that trips on a broken anchor spends its round proving your citation instead of
|
|
161
|
+
your design. Measured cost is near zero (one grep-loop); measured catch: four
|
|
162
|
+
rotted anchors in a single artifact, pre-review.
|
|
163
|
+
|
|
127
164
|
**The verdict travels back as the reviewer's own final output** — the text it
|
|
128
165
|
returns when it finishes, nothing else. A reviewer that tries to message the
|
|
129
166
|
requester mid-run depends on a delivery channel it cannot verify (a subagent
|
|
@@ -301,6 +338,20 @@ When you are the reviewer:
|
|
|
301
338
|
NOT checked here — architecture-awareness is the Capability Ledger / Impact
|
|
302
339
|
review's job; a clause for it would duplicate that.) A lens whose template
|
|
303
340
|
defines no such section (knowledge, marketing) never fires this clause.
|
|
341
|
+
- **Behavioural-claim probes (same reviews; fires only in the lens whose
|
|
342
|
+
SKILL.md defines Execute-Before-Specify — the code lens today; that paragraph
|
|
343
|
+
is the owning definition — cite it, never restate it).** On an L3 design
|
|
344
|
+
artifact, a claim of the form "the code today does X" must trace to an
|
|
345
|
+
assertion in the artifact's shipped harness
|
|
346
|
+
(`ai_docs/solutions/harness_[feature]/`): the check is mechanical — re-run
|
|
347
|
+
the harness and compare, instead of re-deriving the claims by reading. Three
|
|
348
|
+
findings live here and nowhere else: a behavioural claim backed by no probe
|
|
349
|
+
(report it as unproven — reading the source yourself is the same
|
|
350
|
+
insufficient evidence the rule bans for the author); a harness absent while
|
|
351
|
+
the prose carries behavioural claims; and a probe that no longer passes
|
|
352
|
+
against the current tree. Structural facts (a file exists, a symbol's
|
|
353
|
+
signature, its callers) are the blast-radius clause's territory, never probe
|
|
354
|
+
findings. A lens whose SKILL.md defines no such duty never fires this clause.
|
|
304
355
|
|
|
305
356
|
## Anti-patterns
|
|
306
357
|
|
|
@@ -1646,6 +1646,143 @@ def cmd_mark(root, paths):
|
|
|
1646
1646
|
return 0
|
|
1647
1647
|
|
|
1648
1648
|
|
|
1649
|
+
# ------------------------------------------------- orientation-hook wiring
|
|
1650
|
+
# F-041: read-only mirror of the installer-side detection in lib.js
|
|
1651
|
+
# (findOrientHook + orientHookValidator). The JS owns WRITING hooks, with its
|
|
1652
|
+
# injection guards; this side only DETECTS, at `check`, so a project whose
|
|
1653
|
+
# sessions start unoriented hears about it in-project (ENFORCEMENT par.4: the
|
|
1654
|
+
# hook is wired at init only -- pre-F-036 and agent-bootstrapped projects never
|
|
1655
|
+
# got it, and a wired-but-dead hook is "the worst of the three states").
|
|
1656
|
+
# The file list is a SUPERSET of the JS one: .codex/hooks.json is documented in
|
|
1657
|
+
# ENFORCEMENT par.4 but never written by init. Pinned equal on both sides:
|
|
1658
|
+
# the entry-point names, and (F-042) the user-settings path derivation --
|
|
1659
|
+
# CLAUDE_CONFIG_DIR when set, else <home>/.claude -- empty-string env falsy
|
|
1660
|
+
# on both sides.
|
|
1661
|
+
ORIENT_HOOK_ENTRY_POINTS = ("sdlc_check.py", "mkt_check.py")
|
|
1662
|
+
ORIENT_HOOK_CONFIG_FILES = (".claude/settings.json",
|
|
1663
|
+
".claude/settings.local.json",
|
|
1664
|
+
".codex/hooks.json")
|
|
1665
|
+
ORIENT_HOOK_CONFIG_CAP = 1024 * 1024 # settings are tiny; cap pathological files
|
|
1666
|
+
_ORIENT_HOOK_CMD_RE = re.compile(r" orient(\s|$)")
|
|
1667
|
+
_ORIENT_HOOK_TOKEN_RE = re.compile(r'"([^"]*)"|(\S+)')
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
def _orient_entries_in(p):
|
|
1671
|
+
"""Yield the SessionStart commands invoking an orient entry point from ONE
|
|
1672
|
+
client config file. Fail-open: an unreadable, oversized, malformed or
|
|
1673
|
+
directory-shaped file contributes nothing. Reads are utf-8-sig -- a BOM'd
|
|
1674
|
+
settings.json (PowerShell/Notepad) must not read as unwired (declared
|
|
1675
|
+
divergence from Node's JSON.parse)."""
|
|
1676
|
+
try:
|
|
1677
|
+
if not p.is_file() or p.stat().st_size > ORIENT_HOOK_CONFIG_CAP:
|
|
1678
|
+
return
|
|
1679
|
+
data = json.loads(p.read_text(encoding="utf-8-sig"))
|
|
1680
|
+
except Exception:
|
|
1681
|
+
return
|
|
1682
|
+
if not isinstance(data, dict):
|
|
1683
|
+
return
|
|
1684
|
+
hooks = data.get("hooks")
|
|
1685
|
+
groups = hooks.get("SessionStart") if isinstance(hooks, dict) else None
|
|
1686
|
+
if groups is None:
|
|
1687
|
+
groups = data.get("SessionStart") # the .codex/hooks.json shape
|
|
1688
|
+
if not isinstance(groups, list):
|
|
1689
|
+
return
|
|
1690
|
+
for group in groups:
|
|
1691
|
+
if not isinstance(group, dict):
|
|
1692
|
+
continue
|
|
1693
|
+
inner = group.get("hooks")
|
|
1694
|
+
for h in inner if isinstance(inner, list) else [group]:
|
|
1695
|
+
cmd = h.get("command") if isinstance(h, dict) else None
|
|
1696
|
+
if (isinstance(cmd, str)
|
|
1697
|
+
and any(n in cmd for n in ORIENT_HOOK_ENTRY_POINTS)
|
|
1698
|
+
and _ORIENT_HOOK_CMD_RE.search(cmd)):
|
|
1699
|
+
yield cmd
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
def _orient_user_settings_path():
|
|
1703
|
+
"""The machine-global settings file (F-042 wires it at install time).
|
|
1704
|
+
|
|
1705
|
+
Read at CALL time -- a DECLARED divergence from AGENTIC_SDLC_KB_ROOT's
|
|
1706
|
+
import-time read: unittest imports this module once before any test runs,
|
|
1707
|
+
so an import-time seam would be frozen before the matrix could set it.
|
|
1708
|
+
AGENTIC_SDLC_USER_SETTINGS is the test-isolation seam (python-side only);
|
|
1709
|
+
the production derivation mirrors lib.js: CLAUDE_CONFIG_DIR || ~/.claude."""
|
|
1710
|
+
seam = os.environ.get("AGENTIC_SDLC_USER_SETTINGS")
|
|
1711
|
+
if seam:
|
|
1712
|
+
return Path(seam)
|
|
1713
|
+
cfg = os.environ.get("CLAUDE_CONFIG_DIR")
|
|
1714
|
+
return (Path(cfg) if cfg else Path.home() / ".claude") / "settings.json"
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
def _orient_hook_commands(root):
|
|
1718
|
+
"""Yield (command, at_user_level) across every known client config: the
|
|
1719
|
+
three project-relative files, then the machine-global user settings --
|
|
1720
|
+
the file the installer wires (F-042), without which a fixed machine's
|
|
1721
|
+
projects would read as unwired forever."""
|
|
1722
|
+
for rel in ORIENT_HOOK_CONFIG_FILES:
|
|
1723
|
+
for cmd in _orient_entries_in(Path(root) / rel):
|
|
1724
|
+
yield cmd, False
|
|
1725
|
+
for cmd in _orient_entries_in(_orient_user_settings_path()):
|
|
1726
|
+
yield cmd, True
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
def orient_hook_state(root):
|
|
1730
|
+
"""'wired' | 'dead' | 'unwired'.
|
|
1731
|
+
|
|
1732
|
+
A matching entry whose tokens name no validator is "cannot tell" and counts
|
|
1733
|
+
as wired (the lib.js contract: never report broken on cannot-tell). 'dead'
|
|
1734
|
+
requires every matching entry to name a validator that does not resolve:
|
|
1735
|
+
the client runs ALL SessionStart hooks -- project-level AND user-level
|
|
1736
|
+
merge -- so one live entry at either level still orients the session
|
|
1737
|
+
(any-resolves aggregation across levels). Relative tokens resolve against
|
|
1738
|
+
the project root (the vendored-validator convention); a USER-level relative
|
|
1739
|
+
token has no cwd to resolve against and is treated as cannot-tell."""
|
|
1740
|
+
matched = False
|
|
1741
|
+
for cmd, at_user_level in _orient_hook_commands(root):
|
|
1742
|
+
matched = True
|
|
1743
|
+
token = None
|
|
1744
|
+
for m in _ORIENT_HOOK_TOKEN_RE.finditer(cmd):
|
|
1745
|
+
tok = m.group(1) if m.group(1) is not None else m.group(2)
|
|
1746
|
+
if any(tok.endswith(n) for n in ORIENT_HOOK_ENTRY_POINTS):
|
|
1747
|
+
token = tok
|
|
1748
|
+
break
|
|
1749
|
+
if token is None:
|
|
1750
|
+
return "wired"
|
|
1751
|
+
tp = Path(token)
|
|
1752
|
+
if not tp.is_absolute():
|
|
1753
|
+
if at_user_level:
|
|
1754
|
+
return "wired" # cannot tell: no cwd exists at user level
|
|
1755
|
+
tp = Path(root) / tp
|
|
1756
|
+
if tp.exists():
|
|
1757
|
+
return "wired"
|
|
1758
|
+
return "dead" if matched else "unwired"
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
def print_orient_hook_note(root):
|
|
1762
|
+
"""Print the FS-A wiring note (unwired / dead), or nothing when wired.
|
|
1763
|
+
|
|
1764
|
+
Informational only, by contract: never an exit code, never a validate
|
|
1765
|
+
warning (--strict CI must not fail on a hook that legitimately lives in
|
|
1766
|
+
git-ignored settings.local.json). Called by the spine's cmd_check AND by
|
|
1767
|
+
an overlay that replaces check with its own pipeline (the marketing lens
|
|
1768
|
+
does): the note is check-layer behaviour, whoever owns the check."""
|
|
1769
|
+
try:
|
|
1770
|
+
state = orient_hook_state(root)
|
|
1771
|
+
if state == "unwired":
|
|
1772
|
+
print("[note] agent sessions start without automatic orientation on this "
|
|
1773
|
+
"machine (no session-orientation hook found at project or user level). "
|
|
1774
|
+
"Fix once: update or reinstall the skill npm package (on Claude Code "
|
|
1775
|
+
"with Python it wires the hook machine-wide), or wire it per "
|
|
1776
|
+
"ENFORCEMENT.md par.4")
|
|
1777
|
+
elif state == "dead":
|
|
1778
|
+
print("[note] a session-orientation hook is configured but points at a "
|
|
1779
|
+
"validator that no longer exists, so sessions start unoriented. Fix: "
|
|
1780
|
+
"update or reinstall the skill npm package, or correct the command. "
|
|
1781
|
+
"Details: ENFORCEMENT.md par.4")
|
|
1782
|
+
except Exception:
|
|
1783
|
+
pass # detection must never break check
|
|
1784
|
+
|
|
1785
|
+
|
|
1649
1786
|
def cmd_check(root, strict=False, hybrid=False):
|
|
1650
1787
|
print("===== validate =====")
|
|
1651
1788
|
rc_v = cmd_validate(root, strict=strict, hybrid=hybrid)
|
|
@@ -1653,6 +1790,7 @@ def cmd_check(root, strict=False, hybrid=False):
|
|
|
1653
1790
|
rc_s = cmd_stale(root, hybrid=hybrid)
|
|
1654
1791
|
print(f"\ncheck: {'CLEAN' if not (rc_v or rc_s) else 'NOT CLEAN'} "
|
|
1655
1792
|
f"(validate rc={rc_v}, stale rc={rc_s})")
|
|
1793
|
+
print_orient_hook_note(root)
|
|
1656
1794
|
return 1 if (rc_v or rc_s) else 0
|
|
1657
1795
|
|
|
1658
1796
|
|