@antoneeo/agentic-sdlc-skill 1.34.0 → 1.35.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 +21 -0
- package/README.md +204 -186
- package/gemini-extension.json +1 -1
- package/package.json +4 -2
- package/scripts/init.js +6 -17
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +5 -5
- package/skills/agentic-sdlc-skill/SKILL.md +11 -3
- package/skills/agentic-sdlc-skill/memory.md +88 -0
- package/skills/agentic-sdlc-skill/routing.md +7 -1
- package/skills/agentic-sdlc-skill/scripts/knowledge.py +2089 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_check.py +71 -80
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +76 -5
- package/skills/agentic-sdlc-skill/templates.md +1 -1
|
@@ -1,80 +1,71 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def main(argv=None):
|
|
76
|
-
return sdlc_core.main(argv)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == "__main__":
|
|
80
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Domain entry point with shared project memory.
|
|
4
|
+
|
|
5
|
+
Ship this file, sdlc_core.py and knowledge.py together. The core alone omits
|
|
6
|
+
knowledge integrity and this entry point\'s domain-specific behavior.
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
import sdlc_core
|
|
15
|
+
except ImportError as exc: # pragma: no cover - exercised by TS12, not by unit tests
|
|
16
|
+
sys.stderr.write(
|
|
17
|
+
"[ERROR] sdlc_check.py cannot find sdlc_core.py next to it: " + str(exc) + "\n"
|
|
18
|
+
" The validator needs entry point, sdlc_core.py and knowledge.py.\n"
|
|
19
|
+
" Copy all three together; core alone omits memory and overlay checks.\n")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
# Re-export the core's surface: existing importers (`import sdlc_check as sc`)
|
|
23
|
+
# and the test batteries reach for these names on this module.
|
|
24
|
+
from sdlc_core import * # noqa: F401,F403
|
|
25
|
+
from sdlc_core import _map_refs # noqa: F401 underscore helper used by the batteries
|
|
26
|
+
import knowledge
|
|
27
|
+
|
|
28
|
+
# The domain this distribution implements. It does NOT decide any document's
|
|
29
|
+
# owning domain -- that is resolved per project (`default_domain:` in the docs
|
|
30
|
+
# root's README) and per artifact (`domain:`), so the same tree gets the same
|
|
31
|
+
# verdict from every installed distribution. What it decides is which portable
|
|
32
|
+
# checks a document may import here by name; the rest warn as unavailable.
|
|
33
|
+
DOMAIN = "code"
|
|
34
|
+
|
|
35
|
+
sdlc_core.set_entry_point(DOMAIN, provides=("code", "knowledge"))
|
|
36
|
+
|
|
37
|
+
# What this distribution carries. The shared battery reads it; the spine
|
|
38
|
+
# capabilities are not optional, and a shared test refuses a profile that drops one.
|
|
39
|
+
sdlc_core.set_profile(
|
|
40
|
+
skill_name="agentic-sdlc",
|
|
41
|
+
unit_noun="feature",
|
|
42
|
+
support_files=("templates.md", "architect.md", "guides.md", "vision.md", "tdd.md",
|
|
43
|
+
"debugging.md", "elicitation.md", "review.md", "dispatch.md",
|
|
44
|
+
"routing.md", "hybrid.md", "ENFORCEMENT.md", "memory.md"),
|
|
45
|
+
capabilities=(
|
|
46
|
+
# spine
|
|
47
|
+
"triage", "write_triggers", "workstream_registry", "vision_gate",
|
|
48
|
+
"design_review_gate", "guide_router", "worktree_hygiene",
|
|
49
|
+
# code overlay
|
|
50
|
+
"architect_pass", "interaction_contract", "comprehension_guides", "tdd",
|
|
51
|
+
"subagent_dispatch", "legacy_narrative_handoff", "question_discipline",
|
|
52
|
+
),
|
|
53
|
+
design_gate_between=("### 3. Request Analysis", "### 4. Development and Testing"),
|
|
54
|
+
remind_line=(
|
|
55
|
+
"first decide if this skill governs the work; if yes, LOAD it once -- an "
|
|
56
|
+
"unread protocol cannot be applied. Declare the level (L1/L2/L3/Spike) "
|
|
57
|
+
"and the guide-router verdict before acting; an imperative is a request "
|
|
58
|
+
"to triage. Name the mechanism before fixing the first defect you can "
|
|
59
|
+
"measure -- symptom patches recur and stack. Replay governing text -- "
|
|
60
|
+
"doctrine, a prompt, a schema -- against the case that motivated it: red "
|
|
61
|
+
"under the old text, green under the new."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main(argv=None):
|
|
67
|
+
return knowledge.main(argv)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
sys.exit(main())
|
|
@@ -569,6 +569,36 @@ def git_changed_since(root, ref, rel_path):
|
|
|
569
569
|
return None
|
|
570
570
|
|
|
571
571
|
|
|
572
|
+
def git_ref_state(root, ref):
|
|
573
|
+
"""Where a recorded commit stands against HEAD (F-056).
|
|
574
|
+
|
|
575
|
+
'ancestor' -- in HEAD's history; 'orphan' -- a commit HEAD's history does not
|
|
576
|
+
contain (amended, rebased or squash-merged away, or marked on another branch:
|
|
577
|
+
it stops resolving once pruned, and on any fresh clone); 'missing' -- not a
|
|
578
|
+
commit in this repository; 'unknown' -- git gave no yes/no answer.
|
|
579
|
+
Callers pass a hash-shaped ref only: that check is what keeps document
|
|
580
|
+
content from reaching git's argv as an option."""
|
|
581
|
+
try:
|
|
582
|
+
r = subprocess.run(["git", "cat-file", "-e", ref + "^{commit}"],
|
|
583
|
+
cwd=str(root), capture_output=True, text=True, timeout=30)
|
|
584
|
+
if r.returncode != 0:
|
|
585
|
+
return "missing"
|
|
586
|
+
r = subprocess.run(["git", "merge-base", "--is-ancestor", ref, "HEAD"],
|
|
587
|
+
cwd=str(root), capture_output=True, text=True, timeout=30)
|
|
588
|
+
except Exception:
|
|
589
|
+
return "unknown"
|
|
590
|
+
return {0: "ancestor", 1: "orphan"}.get(r.returncode, "unknown")
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def git_is_shallow(root):
|
|
594
|
+
try:
|
|
595
|
+
r = subprocess.run(["git", "rev-parse", "--is-shallow-repository"],
|
|
596
|
+
cwd=str(root), capture_output=True, text=True, timeout=10)
|
|
597
|
+
return r.returncode == 0 and r.stdout.strip() == "true"
|
|
598
|
+
except Exception:
|
|
599
|
+
return False
|
|
600
|
+
|
|
601
|
+
|
|
572
602
|
# -------------------------------------------------------------------- index
|
|
573
603
|
|
|
574
604
|
def build_index(root):
|
|
@@ -1543,6 +1573,11 @@ def cmd_stale(root, hybrid=False):
|
|
|
1543
1573
|
"(audit not initialized, or Hybrid mode where mapping is delegated to devPNT).")
|
|
1544
1574
|
return rc # was: return 0 — MUST carry guide rc
|
|
1545
1575
|
use_git = git_available(root)
|
|
1576
|
+
entry = entry_script()
|
|
1577
|
+
# F-056: an ANALYZED row whose reference cannot be evaluated is UNVERIFIED,
|
|
1578
|
+
# never fresh. Each `continue` below that skips such a row records it here;
|
|
1579
|
+
# skipping silently is how a pruned reference printed `[ok]` over three areas.
|
|
1580
|
+
unverifiable = []
|
|
1546
1581
|
stale = []
|
|
1547
1582
|
for row in rows:
|
|
1548
1583
|
if row["status"] != "ANALYZED":
|
|
@@ -1561,16 +1596,42 @@ def cmd_stale(root, hybrid=False):
|
|
|
1561
1596
|
print(f"[warn] {rel}: path does not exist")
|
|
1562
1597
|
continue
|
|
1563
1598
|
changed = []
|
|
1564
|
-
|
|
1599
|
+
remark = f"re-analyze the area, then run: {entry} mark {rel}"
|
|
1600
|
+
if re.fullmatch(r"[0-9a-fA-F]{7,40}", ref or ""):
|
|
1601
|
+
if not use_git:
|
|
1602
|
+
print(f"[stale] {rel}: reference '{ref}' is a git commit, but git is not usable "
|
|
1603
|
+
"here (not a git work tree, git missing, or the repository refused): its "
|
|
1604
|
+
"freshness cannot be evaluated. Run stale inside the git work tree, or "
|
|
1605
|
+
+ remark)
|
|
1606
|
+
unverifiable.append(rel)
|
|
1607
|
+
continue
|
|
1608
|
+
state = git_ref_state(root, ref)
|
|
1609
|
+
if state == "missing":
|
|
1610
|
+
shallow = (" This clone is shallow: full history may hold it "
|
|
1611
|
+
"(git fetch --unshallow)." if git_is_shallow(root) else "")
|
|
1612
|
+
print(f"[stale] {rel}: git ref '{ref}' is not a commit in this repository "
|
|
1613
|
+
"(amended, rebased or squash-merged away and pruned, or never fetched "
|
|
1614
|
+
f"here): its freshness cannot be evaluated.{shallow} To fix, " + remark)
|
|
1615
|
+
unverifiable.append(rel)
|
|
1616
|
+
continue
|
|
1617
|
+
if state == "orphan":
|
|
1618
|
+
print(f"[warn] {rel}: git ref '{ref}' is not an ancestor of HEAD (amended, "
|
|
1619
|
+
"rebased, squash-merged, or marked on another branch): it stops "
|
|
1620
|
+
"resolving once git prunes it, and on any fresh clone. Re-mark now: "
|
|
1621
|
+
f"{entry} mark {rel}")
|
|
1565
1622
|
res = git_changed_since(root, ref, rel.replace("\\", "/"))
|
|
1566
1623
|
if res is None:
|
|
1567
|
-
print(f"[
|
|
1624
|
+
print(f"[stale] {rel}: git could not compare against ref '{ref}': its "
|
|
1625
|
+
"freshness cannot be evaluated. To fix, " + remark)
|
|
1626
|
+
unverifiable.append(rel)
|
|
1568
1627
|
continue
|
|
1569
1628
|
changed = res
|
|
1570
1629
|
else:
|
|
1571
1630
|
ts = parse_iso(ref)
|
|
1572
1631
|
if ts is None:
|
|
1573
|
-
print(f"[
|
|
1632
|
+
print(f"[stale] {rel}: reference '{ref}' is neither a git commit nor an ISO "
|
|
1633
|
+
"UTC timestamp: its freshness cannot be evaluated. To fix, " + remark)
|
|
1634
|
+
unverifiable.append(rel)
|
|
1574
1635
|
continue
|
|
1575
1636
|
for fp in iter_files(target):
|
|
1576
1637
|
mtime = datetime.fromtimestamp(fp.stat().st_mtime, tz=timezone.utc)
|
|
@@ -1583,8 +1644,11 @@ def cmd_stale(root, hybrid=False):
|
|
|
1583
1644
|
if changed:
|
|
1584
1645
|
stale.append((rel, changed))
|
|
1585
1646
|
|
|
1647
|
+
if unverifiable:
|
|
1648
|
+
rc = 1 # unverified is not fresh (F-056)
|
|
1586
1649
|
if not stale:
|
|
1587
|
-
|
|
1650
|
+
if not unverifiable:
|
|
1651
|
+
print("[ok] no analyzed area was modified after its last recorded analysis.")
|
|
1588
1652
|
return rc # was: return 0 — MUST carry guide rc
|
|
1589
1653
|
print("Areas modified after the last recorded analysis:")
|
|
1590
1654
|
for rel, changed in stale:
|
|
@@ -1593,7 +1657,7 @@ def cmd_stale(root, hybrid=False):
|
|
|
1593
1657
|
print(f" - {c}")
|
|
1594
1658
|
if len(changed) > 10:
|
|
1595
1659
|
print(f" ... and {len(changed) - 10} more")
|
|
1596
|
-
print("\nAfter re-analyzing, record it with:
|
|
1660
|
+
print(f"\nAfter re-analyzing, record it with: {entry} mark <path>")
|
|
1597
1661
|
return 1 # stale areas dominate: rc already implied
|
|
1598
1662
|
|
|
1599
1663
|
|
|
@@ -1645,6 +1709,13 @@ def cmd_mark(root, paths):
|
|
|
1645
1709
|
|
|
1646
1710
|
f.parent.mkdir(parents=True, exist_ok=True)
|
|
1647
1711
|
f.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
1712
|
+
if use_git_ref:
|
|
1713
|
+
# F-056: the reference is born here, so this is where the gestures that
|
|
1714
|
+
# orphan it are named. An amend was the observed one.
|
|
1715
|
+
print(f"[info] {ref} is the current HEAD. Commit this audit plan change as a new "
|
|
1716
|
+
"commit: amending, rebasing or squash-merging that commit orphans the "
|
|
1717
|
+
f"reference, and `{entry_script()} stale` fails once it no longer resolves. "
|
|
1718
|
+
"After a squash or rebase merge, re-mark on the integration branch.")
|
|
1648
1719
|
return 0
|
|
1649
1720
|
|
|
1650
1721
|
|
|
@@ -436,7 +436,7 @@ as pending. A ledger id absent from the plan is a non-fatal orphan warning.
|
|
|
436
436
|
|
|
437
437
|
## ai_docs/audit/audit_plan.md (Standalone mode only)
|
|
438
438
|
|
|
439
|
-
The `Reference` field (git hash or ISO UTC timestamp) is managed by `sdlc_check.py mark` — do not fill it by hand. Freshness is verified with `sdlc_check.py stale`.
|
|
439
|
+
The `Reference` field (git hash or ISO UTC timestamp) is managed by `sdlc_check.py mark` — do not fill it by hand. Freshness is verified with `sdlc_check.py stale`. Commit the `mark` result as a new commit: amending, rebasing or squash-merging the commit it recorded orphans the reference, and `stale` fails once it no longer resolves — after a squash or rebase merge, re-mark on the integration branch.
|
|
440
440
|
|
|
441
441
|
```markdown
|
|
442
442
|
# Audit Plan
|