@event4u/agent-config 1.19.0 → 1.20.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 (88) hide show
  1. package/.agent-src/commands/agent-handoff.md +14 -10
  2. package/.agent-src/commands/chat-history/import.md +170 -0
  3. package/.agent-src/commands/chat-history/learn.md +178 -0
  4. package/.agent-src/commands/chat-history/show.md +17 -18
  5. package/.agent-src/commands/chat-history.md +26 -25
  6. package/.agent-src/commands/council/default.md +4 -7
  7. package/.agent-src/commands/create-pr.md +28 -8
  8. package/.agent-src/commands/sync-gitignore.md +1 -1
  9. package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +76 -0
  10. package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +3 -3
  11. package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +5 -12
  12. package/.agent-src/rules/direct-answers.md +10 -2
  13. package/.agent-src/rules/language-and-tone.md +37 -6
  14. package/.agent-src/rules/no-attribution-footers.md +48 -0
  15. package/.agent-src/rules/no-roadmap-references.md +1 -1
  16. package/.agent-src/rules/skill-quality.md +49 -0
  17. package/.agent-src/rules/user-interaction.md +21 -5
  18. package/.agent-src/skills/ai-council/SKILL.md +4 -5
  19. package/.agent-src/skills/dcf-modeling/SKILL.md +89 -0
  20. package/.agent-src/skills/funnel-analysis/SKILL.md +100 -0
  21. package/.agent-src/skills/md-language-check/SKILL.md +1 -1
  22. package/.agent-src/skills/okr-tree-modeling/SKILL.md +93 -0
  23. package/.agent-src/skills/rice-prioritization/SKILL.md +100 -0
  24. package/.agent-src/skills/subagent-orchestration/SKILL.md +34 -2
  25. package/.agent-src/skills/unit-economics-modeling/SKILL.md +104 -0
  26. package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
  27. package/.agent-src/templates/agent-settings.md +5 -26
  28. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +7 -5
  29. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +0 -4
  30. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +0 -4
  31. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +7 -51
  32. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +1 -2
  33. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +1 -2
  34. package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +2 -3
  35. package/.agent-src/templates/skill.md +30 -1
  36. package/.claude-plugin/marketplace.json +8 -4
  37. package/AGENTS.md +44 -3
  38. package/CHANGELOG.md +111 -0
  39. package/README.md +6 -6
  40. package/config/agent-settings.template.yml +19 -13
  41. package/config/gitignore-block.txt +4 -4
  42. package/docs/architecture.md +3 -3
  43. package/docs/catalog.md +14 -12
  44. package/docs/contracts/adr-chat-history-split.md +10 -1
  45. package/docs/contracts/command-clusters.md +1 -1
  46. package/docs/contracts/cross-wing-handoff.md +133 -0
  47. package/docs/contracts/file-ownership-matrix.json +341 -126
  48. package/docs/contracts/hook-architecture-v1.md +8 -1
  49. package/docs/contracts/memory-visibility-v1.md +8 -24
  50. package/docs/customization.md +1 -1
  51. package/docs/getting-started.md +21 -29
  52. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +1 -1
  53. package/docs/hook-payload-capture.md +221 -0
  54. package/docs/migrations/commands-1.15.0.md +17 -12
  55. package/docs/skills-catalog.md +5 -4
  56. package/llms.txt +4 -3
  57. package/package.json +1 -1
  58. package/scripts/agent-config +1 -1
  59. package/scripts/ai_council/_default_prices.py +4 -4
  60. package/scripts/ai_council/clients.py +1 -1
  61. package/scripts/ai_council/modes.py +3 -4
  62. package/scripts/ai_council/pricing.py +10 -9
  63. package/scripts/build_rule_trigger_matrix.py +1 -9
  64. package/scripts/chat_history.py +952 -596
  65. package/scripts/check_references.py +12 -2
  66. package/scripts/council_cli.py +54 -4
  67. package/scripts/hook_manifest.yaml +33 -0
  68. package/scripts/hooks/augment-chat-history.sh +10 -0
  69. package/scripts/hooks/cowork-dispatcher.sh +98 -0
  70. package/scripts/hooks/dispatch_hook.py +35 -0
  71. package/scripts/hooks_status.py +12 -1
  72. package/scripts/install-hooks.sh +2 -2
  73. package/scripts/install.sh +37 -0
  74. package/scripts/lint_handoffs.py +214 -0
  75. package/scripts/lint_hook_manifest.py +2 -1
  76. package/scripts/redact_hook_capture.py +148 -0
  77. package/scripts/schemas/skill.schema.json +5 -0
  78. package/scripts/skill_linter.py +163 -1
  79. package/scripts/update_prices.py +3 -3
  80. package/.agent-src/commands/chat-history/checkpoint.md +0 -126
  81. package/.agent-src/commands/chat-history/clear.md +0 -103
  82. package/.agent-src/commands/chat-history/resume.md +0 -183
  83. package/.agent-src/rules/chat-history-cadence.md +0 -143
  84. package/.agent-src/rules/chat-history-ownership.md +0 -124
  85. package/.agent-src/rules/chat-history-visibility.md +0 -97
  86. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +0 -50
  87. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +0 -49
  88. package/scripts/check_phase_coupling.py +0 -148
@@ -57,7 +57,8 @@ EVENT_VOCABULARY: set[str] = {
57
57
  # Known platform identifiers. New platforms MUST be added here as they
58
58
  # land — the linter is the gate that proves no orphan slot escapes.
59
59
  KNOWN_PLATFORMS: set[str] = {
60
- "augment", "claude", "cursor", "cline", "windsurf", "gemini", "copilot",
60
+ "augment", "claude", "cowork",
61
+ "cursor", "cline", "windsurf", "gemini", "copilot",
61
62
  }
62
63
 
63
64
 
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ """Redact captured hook payloads for the verified-platforms roadmap.
3
+
4
+ Reads JSON capture files written by ``dispatch_hook.py`` (when
5
+ ``AGENT_HOOK_CAPTURE_DIR`` is set) and produces a redacted version
6
+ suitable for pasting into
7
+ ``agents/roadmaps/road-to-verified-chat-history-platforms.md``.
8
+
9
+ Redaction policy (per the roadmap's Capture-and-redact protocol):
10
+
11
+ - Replace string values at known user-content paths with
12
+ ``<REDACTED>``. Default field allowlist mirrors the fallback list
13
+ in ``scripts/chat_history.py::_extract_hook_text`` plus Augment's
14
+ nested ``conversation.*`` shape.
15
+ - Preserve envelope keys (``hook_event_name``, ``session_id``,
16
+ ``platform``, ``event``, ``cwd``, ``workspace_roots``,
17
+ ``transcript_path``, ``model``, ``cursor_version``, …) so the
18
+ schema is reviewable.
19
+ - ``--strict`` redacts any string longer than ``--max-len`` (default
20
+ 120) chars regardless of key, as a safety net for unknown fields.
21
+
22
+ Usage:
23
+
24
+ python3 scripts/redact_hook_capture.py <input> [--out <path>] [--strict]
25
+
26
+ Input may be a single JSON file or a directory; with a directory,
27
+ every ``*.json`` is redacted and written next to the original with
28
+ the suffix ``.redacted.json``.
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import json
34
+ import sys
35
+ from pathlib import Path
36
+ from typing import Any
37
+
38
+ REDACTED = "<REDACTED>"
39
+
40
+ # Field names that carry user / agent content (from
41
+ # scripts/chat_history.py::_extract_hook_text fallback list +
42
+ # nested Augment shape). Matched case-insensitively against the
43
+ # leaf key.
44
+ _USER_CONTENT_KEYS = {
45
+ "prompt", "user_prompt", "userprompt", "first_user_msg",
46
+ "firstusermsg", "usermessage", "user_message", "text",
47
+ "response", "message", "content",
48
+ # Augment Code with includeConversationData
49
+ "agenttextresponse", "agent_text_response",
50
+ "agentcoderesponse", "agent_code_response",
51
+ # Cursor / generic
52
+ "submitted_prompt", "submittedprompt",
53
+ # Free-form transcript bodies (path stays — content is in another file)
54
+ "transcript", "transcript_text",
55
+ }
56
+
57
+ # Keys whose value is a structural / schema marker — keep as-is even
58
+ # when --strict would otherwise redact long values.
59
+ _ENVELOPE_KEYS_KEEP = {
60
+ "hook_event_name", "session_id", "transcript_path", "transcriptpath",
61
+ "platform", "event", "native_event", "captured_at", "cwd",
62
+ "workspace_roots", "model", "cursor_version", "user_email",
63
+ "conversation_id", "generation_id", "agent", "type",
64
+ "schema_version", "started_at", "completed_at", "_raw_text",
65
+ "path", "changetype", "change_type",
66
+ }
67
+
68
+
69
+ def _redact_value(val: Any, *, key: str | None, strict: bool,
70
+ max_len: int) -> Any:
71
+ """Recursively redact a value."""
72
+ norm_key = (key or "").lower().replace("-", "_")
73
+ if isinstance(val, dict):
74
+ return {k: _redact_value(v, key=k, strict=strict, max_len=max_len)
75
+ for k, v in val.items()}
76
+ if isinstance(val, list):
77
+ return [_redact_value(item, key=key, strict=strict, max_len=max_len)
78
+ for item in val]
79
+ if isinstance(val, str):
80
+ if norm_key in _ENVELOPE_KEYS_KEEP:
81
+ return val
82
+ if norm_key in _USER_CONTENT_KEYS:
83
+ return REDACTED
84
+ if strict and len(val) > max_len:
85
+ return REDACTED
86
+ return val
87
+ return val
88
+
89
+
90
+ def redact(record: dict, *, strict: bool = False, max_len: int = 120) -> dict:
91
+ """Redact a single capture record. Top-level envelope is preserved."""
92
+ out: dict = {}
93
+ for k, v in record.items():
94
+ if k == "raw_payload":
95
+ out[k] = _redact_value(v, key=None, strict=strict,
96
+ max_len=max_len)
97
+ else:
98
+ out[k] = _redact_value(v, key=k, strict=strict,
99
+ max_len=max_len)
100
+ return out
101
+
102
+
103
+ def _process_file(path: Path, *, out: Path | None, strict: bool,
104
+ max_len: int) -> Path:
105
+ record = json.loads(path.read_text(encoding="utf-8"))
106
+ redacted = redact(record, strict=strict, max_len=max_len)
107
+ target = out or path.with_suffix(".redacted.json")
108
+ target.write_text(json.dumps(redacted, indent=2) + "\n",
109
+ encoding="utf-8")
110
+ return target
111
+
112
+
113
+ def main(argv: list[str] | None = None) -> int:
114
+ parser = argparse.ArgumentParser(description=__doc__)
115
+ parser.add_argument("input", help="capture file or directory")
116
+ parser.add_argument("--out", default=None,
117
+ help="output path (single-file mode only)")
118
+ parser.add_argument("--strict", action="store_true",
119
+ help="redact any string longer than --max-len")
120
+ parser.add_argument("--max-len", type=int, default=120,
121
+ help="strict-mode length threshold (default 120)")
122
+ args = parser.parse_args(argv)
123
+
124
+ src = Path(args.input).expanduser()
125
+ if not src.exists():
126
+ sys.stderr.write(f"redact: input not found: {src}\n")
127
+ return 2
128
+
129
+ if src.is_dir():
130
+ if args.out:
131
+ sys.stderr.write("redact: --out is single-file only\n")
132
+ return 2
133
+ files = sorted(p for p in src.glob("*.json")
134
+ if not p.name.endswith(".redacted.json"))
135
+ for path in files:
136
+ target = _process_file(path, out=None, strict=args.strict,
137
+ max_len=args.max_len)
138
+ print(f"redacted: {target}")
139
+ return 0
140
+
141
+ target = _process_file(src, out=Path(args.out) if args.out else None,
142
+ strict=args.strict, max_len=args.max_len)
143
+ print(f"redacted: {target}")
144
+ return 0
145
+
146
+
147
+ if __name__ == "__main__":
148
+ raise SystemExit(main())
@@ -37,6 +37,11 @@
37
37
  "pattern": "^[a-z][a-z0-9-]*$"
38
38
  }
39
39
  },
40
+ "tier": {
41
+ "type": "string",
42
+ "enum": ["senior"],
43
+ "description": "Optional tier marker. `senior` opts the skill into the Senior-Tier Required Structure check (Context-First lead, Related Skills, Proactive Triggers, Output Artifacts) per .agent-src.uncompressed/rules/skill-quality.md."
44
+ },
40
45
  "execution": {
41
46
  "type": "object",
42
47
  "additionalProperties": false,
@@ -99,6 +99,17 @@ TYPE_PATTERN = re.compile(r'^type:\s*"?(always|auto)"?\s*$', re.MULTILINE)
99
99
  SOURCE_PATTERN = re.compile(r'^source:\s*"?(package|project)"?\s*$', re.MULTILINE)
100
100
  STATUS_PATTERN = re.compile(r'^status:\s*"?(active|deprecated|superseded)"?\s*$', re.MULTILINE)
101
101
  REPLACED_BY_PATTERN = re.compile(r'^replaced_by:\s*"?([\w-]+)"?\s*$', re.MULTILINE)
102
+ TIER_PATTERN = re.compile(r'^tier:\s*"?([\w-]+)"?\s*$', re.MULTILINE)
103
+
104
+ # --- Senior-tier required-block patterns (skill-quality.md § Senior-Tier Required Structure) ---
105
+ # Heading-only checks; detail-shape lives in skill-quality-mechanics.md.
106
+ SENIOR_RELATED_SKILLS_PATTERN = re.compile(r"^##\s+Related Skills\s*$", re.MULTILINE)
107
+ SENIOR_RELATED_WHEN_PATTERN = re.compile(r"\*\*WHEN to use this\*\*", re.IGNORECASE)
108
+ SENIOR_RELATED_WHEN_NOT_PATTERN = re.compile(r"\*\*WHEN NOT to use this\*\*", re.IGNORECASE)
109
+ SENIOR_PROACTIVE_PATTERN = re.compile(
110
+ r"^##\s+When the agent should load this\s*$", re.MULTILINE
111
+ )
112
+ SENIOR_OUTPUT_PATTERN = re.compile(r"^##\s+Output\s*$", re.MULTILINE)
102
113
  H1_PATTERN = re.compile(r"^# .+", re.MULTILINE)
103
114
  DOUBLE_BLANK_PATTERN = re.compile(r"\n{3,}")
104
115
 
@@ -415,6 +426,11 @@ def lint_skill(path: Path, text: str) -> LintResult:
415
426
  if execution is not None:
416
427
  issues.extend(lint_execution_metadata(execution))
417
428
 
429
+ # --- Senior-tier required-block check (skill-quality.md § Senior-Tier Required Structure) ---
430
+ tier_match = TIER_PATTERN.search(frontmatter)
431
+ if tier_match and tier_match.group(1) == "senior":
432
+ issues.extend(lint_senior_tier_blocks(text))
433
+
418
434
  procedure_block = find_procedure_block(text)
419
435
  if procedure_block is not None:
420
436
  if not procedure_block:
@@ -603,6 +619,57 @@ def parse_execution_block(frontmatter: str) -> Optional[dict]:
603
619
  return result
604
620
 
605
621
 
622
+ def lint_senior_tier_blocks(text: str) -> List[Issue]:
623
+ """Validate the four required blocks for `tier: senior` skills.
624
+
625
+ Per .agent-src.uncompressed/rules/skill-quality.md § Senior-Tier
626
+ Required Structure: Context-First lead (description), Related Skills
627
+ (with WHEN / WHEN NOT lists), Proactive Triggers, Output Artifacts.
628
+
629
+ The Context-First lead is checked structurally via description length
630
+ + content; here we enforce the three section blocks and the WHEN /
631
+ WHEN NOT two-list pattern inside Related Skills.
632
+ """
633
+ issues: List[Issue] = []
634
+
635
+ if not SENIOR_RELATED_SKILLS_PATTERN.search(text):
636
+ issues.append(Issue(
637
+ "error",
638
+ "missing_senior_related_skills",
639
+ "Senior-tier skill missing `## Related Skills` block (skill-quality.md § Senior-Tier Required Structure)",
640
+ ))
641
+ else:
642
+ related_block = extract_section_block(text, "Related Skills") or ""
643
+ if not SENIOR_RELATED_WHEN_PATTERN.search(related_block):
644
+ issues.append(Issue(
645
+ "error",
646
+ "missing_senior_related_when",
647
+ "Senior-tier `## Related Skills` block missing `**WHEN to use this**` list",
648
+ ))
649
+ if not SENIOR_RELATED_WHEN_NOT_PATTERN.search(related_block):
650
+ issues.append(Issue(
651
+ "error",
652
+ "missing_senior_related_when_not",
653
+ "Senior-tier `## Related Skills` block missing `**WHEN NOT to use this**` list",
654
+ ))
655
+
656
+ if not SENIOR_PROACTIVE_PATTERN.search(text):
657
+ issues.append(Issue(
658
+ "error",
659
+ "missing_senior_proactive_triggers",
660
+ "Senior-tier skill missing `## When the agent should load this` block",
661
+ ))
662
+
663
+ if not SENIOR_OUTPUT_PATTERN.search(text):
664
+ issues.append(Issue(
665
+ "error",
666
+ "missing_senior_output_artifacts",
667
+ "Senior-tier skill missing `## Output` block declaring artifact name + shape",
668
+ ))
669
+
670
+ return issues
671
+
672
+
606
673
  def lint_execution_metadata(execution: dict) -> List[Issue]:
607
674
  """Validate the execution block of a skill."""
608
675
  issues: List[Issue] = []
@@ -1656,6 +1723,70 @@ def lint_governance(path: Path, text: str, artifact_type: str, repo_root: Path |
1656
1723
  return issues
1657
1724
 
1658
1725
 
1726
+ # --- Structural malice check (see road-to-suite-closure Phase 5) ---
1727
+ #
1728
+ # Five regex patterns scan skill / rule / command bodies for **structural**
1729
+ # (not semantic) malice. Findings surface as ``Issue("error",
1730
+ # "malice:<pattern>", "<line>:<matched>")`` so ``compute_exit_code`` can
1731
+ # emit exit code 3 (security-failure), distinct from 2 (build-failure).
1732
+ # Semantic checks (PII leakage, prompt injection) are deferred to v2.
1733
+
1734
+ # (a) credential exfil — curl|wget piping ${TOKEN}/${KEY}/${SECRET}/...
1735
+ # env vars or hitting ~/.aws/ ~/.ssh/ secrets.
1736
+ _MALICE_CRED_EXFIL = re.compile(
1737
+ r"\b(?:curl|wget)\b[^\n]*"
1738
+ r"(?:\$\{?[A-Z_]*(?:TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|API)[A-Z_]*\}?"
1739
+ r"|~/\.(?:aws|ssh)/)"
1740
+ )
1741
+ # (b) arbitrary execution — eval/exec over a network-fetched payload, or
1742
+ # `bash <(curl ...)` / `sh <(wget ...)` style remote-execution.
1743
+ _MALICE_REMOTE_EXEC = re.compile(
1744
+ r"(?:\b(?:eval|exec)\s*\([^)]*(?:curl|wget|requests\.get|urllib)"
1745
+ r"|\b(?:bash|sh|zsh)\s*<\s*\(\s*(?:curl|wget))"
1746
+ )
1747
+ # (c) force-push to a protected ref.
1748
+ _MALICE_FORCE_PUSH = re.compile(
1749
+ r"\bgit\s+push\b[^\n]*--force(?:-with-lease)?\b[^\n]*"
1750
+ r"\b(?:main|master|prod|production|release)\b"
1751
+ )
1752
+ # (d) world-readable secrets — chmod 0?[4567]xx on .pem/.key/.env files.
1753
+ _MALICE_CHMOD_SECRETS = re.compile(
1754
+ r"\bchmod\s+0?[4567]\d{2}\s+[^\n]*\.(?:pem|key|env)\b"
1755
+ )
1756
+ # (e) unbounded subprocess shell injection — shell=True interpolating ${VAR}.
1757
+ _MALICE_SHELL_INJECT = re.compile(
1758
+ r"\bsubprocess\.[A-Za-z_]+\s*\([^)]*shell\s*=\s*True[^)]*\$\{"
1759
+ )
1760
+
1761
+ _MALICE_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
1762
+ ("cred_exfil", _MALICE_CRED_EXFIL),
1763
+ ("remote_exec", _MALICE_REMOTE_EXEC),
1764
+ ("force_push_protected", _MALICE_FORCE_PUSH),
1765
+ ("chmod_secrets", _MALICE_CHMOD_SECRETS),
1766
+ ("shell_injection", _MALICE_SHELL_INJECT),
1767
+ ]
1768
+
1769
+
1770
+ def check_structural_malice(text: str) -> List[Issue]:
1771
+ """Return one Issue per malice match. Empty list when clean.
1772
+
1773
+ Issue shape: ``Issue("error", f"malice:{name}", f"{line}:{matched}")``.
1774
+ The ``format_text`` renderer special-cases the ``malice:`` code prefix
1775
+ to emit ``<path>:<line>:malice:<pattern>:<matched>`` per Phase 5.2.
1776
+ """
1777
+ issues: List[Issue] = []
1778
+ for lineno, raw in enumerate(text.splitlines(), start=1):
1779
+ for name, pattern in _MALICE_PATTERNS:
1780
+ match = pattern.search(raw)
1781
+ if match:
1782
+ issues.append(Issue(
1783
+ severity="error",
1784
+ code=f"malice:{name}",
1785
+ message=f"{lineno}:{match.group(0).strip()}",
1786
+ ))
1787
+ return issues
1788
+
1789
+
1659
1790
  # --- Output-schema check (see road-to-trigger-evals Phase 3.5) ---
1660
1791
  #
1661
1792
  # Skills that freeze an output shape (`refine-ticket`, `estimate-ticket`)
@@ -1865,11 +1996,36 @@ def lint_file(path: Path, repo_root: Path | None = None) -> LintResult:
1865
1996
  result.issues.extend(schema_issues)
1866
1997
  result.status = classify_status(result.issues)
1867
1998
 
1999
+ # Post-processing: structural malice scan (errors). Skills, rules,
2000
+ # and commands carry executable patterns; guidelines/personas are
2001
+ # prose-only and skipped to keep noise low.
2002
+ if artifact_type in ("skill", "rule", "command"):
2003
+ malice_issues = check_structural_malice(text)
2004
+ if malice_issues:
2005
+ result.issues.extend(malice_issues)
2006
+ result.status = classify_status(result.issues)
2007
+
1868
2008
  return result
1869
2009
 
1870
2010
 
1871
2011
  def format_text(results: list[LintResult]) -> str:
1872
2012
  lines: list[str] = []
2013
+ # Phase 5.2: malice findings render in the spec shape
2014
+ # ``<path>:<line>:malice:<pattern>:<matched>`` ahead of the badge
2015
+ # block so security-failures are grep-able from the top.
2016
+ malice_total = 0
2017
+ for result in results:
2018
+ for issue in result.issues:
2019
+ if issue.code.startswith("malice:"):
2020
+ pattern_name = issue.code.split(":", 1)[1]
2021
+ lineno, _, matched = issue.message.partition(":")
2022
+ lines.append(
2023
+ f"{result.file}:{lineno}:malice:{pattern_name}:{matched}"
2024
+ )
2025
+ malice_total += 1
2026
+ if malice_total:
2027
+ lines.append("")
2028
+
1873
2029
  for result in results:
1874
2030
  badge = {"pass": "[PASS]", "pass_with_warnings": "[WARN]", "fail": "[FAIL]"}[result.status]
1875
2031
  lines.append(f"{badge} {result.file} ({result.artifact_type})")
@@ -1888,7 +2044,8 @@ def format_text(results: list[LintResult]) -> str:
1888
2044
  fails = sum(1 for r in results if r.status == "fail")
1889
2045
  warns = sum(1 for r in results if r.status == "pass_with_warnings")
1890
2046
  passes = sum(1 for r in results if r.status == "pass")
1891
- lines.append(f"Summary: {passes} pass, {warns} warn, {fails} fail, {total} total")
2047
+ suffix = f", {malice_total} malice" if malice_total else ""
2048
+ lines.append(f"Summary: {passes} pass, {warns} warn, {fails} fail, {total} total{suffix}")
1892
2049
  return "\n".join(lines)
1893
2050
 
1894
2051
 
@@ -2087,6 +2244,11 @@ def check_duplication(root: Path) -> list[LintResult]:
2087
2244
 
2088
2245
 
2089
2246
  def compute_exit_code(results: list[LintResult], strict_warnings: bool) -> int:
2247
+ # Phase 5.2: structural-malice findings emit exit code 3 (security-
2248
+ # failure), distinct from 2 (build-failure) so CI surfaces can split.
2249
+ for r in results:
2250
+ if any(issue.code.startswith("malice:") for issue in r.issues):
2251
+ return 3
2090
2252
  if any(r.status == "fail" for r in results):
2091
2253
  return 2
2092
2254
  if any(r.status == "pass_with_warnings" for r in results) and strict_warnings:
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """Refresh `.agent-prices.md` from the LiteLLM model-prices feed.
2
+ """Refresh `agents/.agent-prices.md` from the LiteLLM model-prices feed.
3
3
 
4
4
  Source: https://raw.githubusercontent.com/BerriAI/litellm/main/
5
5
  model_prices_and_context_window.json
@@ -9,7 +9,7 @@ Network failure or invalid response → fall back to
9
9
  always written. Stdlib only; no extra dependency.
10
10
 
11
11
  Usage:
12
- python3 scripts/update_prices.py # writes .agent-prices.md
12
+ python3 scripts/update_prices.py # writes agents/.agent-prices.md
13
13
  python3 scripts/update_prices.py --check # exit 1 if file is stale
14
14
  """
15
15
 
@@ -81,7 +81,7 @@ def _to_rows_from_litellm(payload: dict[str, dict[str, object]]) -> list[tuple[s
81
81
 
82
82
 
83
83
  def refresh(path: Path = PRICES_FILE) -> str:
84
- """Write a fresh `.agent-prices.md`. Returns the source label used."""
84
+ """Write a fresh `agents/.agent-prices.md`. Returns the source label used."""
85
85
  payload = _fetch_litellm()
86
86
  if payload is not None:
87
87
  rows = _to_rows_from_litellm(payload)
@@ -1,126 +0,0 @@
1
- ---
2
- name: chat-history:checkpoint
3
- cluster: chat-history
4
- sub: checkpoint
5
- description: Append a phase-boundary entry to .agent-chat-history — CHECKPOINT fallback for platforms without a native hook (Augment IDE, Cursor pre-1.7, Cline non-Mac/Linux). ~1s.
6
- disable-model-invocation: true
7
- suggestion:
8
- eligible: true
9
- trigger_description: "User wants to flush a chat-history phase boundary on a CHECKPOINT-class platform (Augment IDE, Cursor < 1.7, Cline on Windows) — phrases like 'checkpoint chat history', 'log a phase boundary', 'manual chat-history append'."
10
- trigger_context: "chat_history.path == checkpoint AND a phase-shaped boundary just completed (decision recorded, multi-tool sequence finished, task-list item closed)."
11
- ---
12
- <!-- cloud_safe: noop -->
13
-
14
- # /chat-history checkpoint
15
- Force-append a `phase`-typed entry to `.agent-chat-history`. CHECKPOINT
16
- fallback for platforms without native hooks — see
17
- [`agents/contexts/chat-history-platform-hooks.md`](../../../agents/contexts/chat-history-platform-hooks.md)
18
- for the per-platform classification.
19
-
20
- Use this at decision points, end of phase, or after a meaningful tool
21
- sequence on a CHECKPOINT/MANUAL platform. On HOOK platforms (Claude
22
- Code, Augment CLI, Cursor 1.7+, Cline non-Windows, Windsurf, Gemini
23
- CLI), the platform fires hooks automatically — manual use is allowed
24
- but rarely needed.
25
-
26
- ## When to use
27
-
28
- - IDE plugin without hooks (Augment Code IDE plugin as of 2026-04-30).
29
- - Long-running tool sequence on any platform that did not flush.
30
- - Explicit phase-boundary marker after a multi-tool refactor.
31
- - Crash-recovery rehearsal — verifies the append path works before a
32
- real outage.
33
-
34
- ## When NOT to use
35
-
36
- - HOOK platform mid-session — the platform already records turn-level
37
- cadence; an extra checkpoint just adds noise.
38
- - After every line of agent output — that's `per_turn` cadence and is
39
- configured in `.agent-settings.yml`, not via this command.
40
- - To inspect the log → [`/chat-history`](chat-history.md).
41
- - To wipe the log → [`/chat-history-clear`](chat-history-clear.md).
42
- - To reload the log into context → [`/chat-history-resume`](chat-history-resume.md).
43
-
44
- ## Steps
45
-
46
- ### 1. Check if enabled
47
-
48
- Read `chat_history.enabled` from `.agent-settings.yml`. If `false` or
49
- the section is missing, say so and stop:
50
-
51
- ```
52
- > 📒 chat-history is disabled (chat_history.enabled = false).
53
- > Nothing to checkpoint. Enable in .agent-settings.yml first.
54
- ```
55
-
56
- ### 2. Determine the phase label
57
-
58
- Pick a short label (2–6 words) that names what just happened:
59
-
60
- - "phase-1-done", "refactor-extracted", "tests-green",
61
- "review-comments-fixed", "merge-ready".
62
-
63
- If the user invoked the command without context, ask once with
64
- numbered options per [`user-interaction`](../rules/user-interaction.md):
65
-
66
- ```
67
- > 1. Use a free-text label — type 2–6 words for the checkpoint
68
- > 2. Use a generic "manual-checkpoint" label
69
- > 3. Skip — close without writing
70
- ```
71
-
72
- ### 3. Append the checkpoint
73
-
74
- Invoke the master CLI:
75
-
76
- ```
77
- ./agent-config chat-history:checkpoint \
78
- --first-user-msg "<the conversation's first user message>" \
79
- --payload '{"phase": "<label>"}'
80
- ```
81
-
82
- The wrapper delegates to `scripts/chat_history.py hook-append --event phase`,
83
- which performs cadence filtering and ownership checks. Cadence is read
84
- from `chat_history.frequency` in `.agent-settings.yml` — `per_turn` /
85
- `per_phase` / `per_tool`. `per_tool` cadence drops the `phase` event;
86
- say so explicitly if that is the active mode.
87
-
88
- ### 4. Confirm
89
-
90
- Render a one-line confirmation, mirroring the user's language:
91
-
92
- ```
93
- > 📒 Checkpoint logged: <label> (entries: N → N+1)
94
- ```
95
-
96
- If the helper returned `skipped_cadence`, surface it:
97
-
98
- ```
99
- > 📒 Checkpoint skipped — current cadence is per_tool, phase events are dropped.
100
- > Switch chat_history.frequency to per_phase or per_turn to capture phase boundaries.
101
- ```
102
-
103
- ## Gotchas
104
-
105
- - The command writes through the same ownership-state machine as
106
- hooks — a `foreign` log triggers the
107
- [`chat-history`](../rules/chat-history-ownership.md) Foreign-Prompt before any
108
- append. This is intentional; the checkpoint must not silently
109
- hijack another session's log.
110
- - The `phase` payload key is required. Other keys are accepted but
111
- ignored by the JSONL schema (forward-compat — they may be promoted
112
- to first-class fields later).
113
- - On HOOK platforms, hook entries and checkpoint entries coexist
114
- cleanly. The schema does not deduplicate; if you checkpoint
115
- immediately after a hook fires, expect two adjacent entries with
116
- different `source` values.
117
-
118
- ## See also
119
-
120
- - [`chat-history`](../rules/chat-history-ownership.md) — the rule defining the
121
- conditional Iron Law (HOOK platforms vs CHECKPOINT/MANUAL platforms)
122
- - [`/chat-history`](chat-history.md) — read-only status display
123
- - [`/chat-history-resume`](chat-history-resume.md) — adopt + load
124
- - [`/chat-history-clear`](chat-history-clear.md) — wipe
125
- - [`agents/contexts/chat-history-platform-hooks.md`](../../../agents/contexts/chat-history-platform-hooks.md) — per-platform strategy table
126
- - [`scripts/chat_history.py`](../../../scripts/chat_history.py) — `hook-append` API
@@ -1,103 +0,0 @@
1
- ---
2
- name: chat-history:clear
3
- cluster: chat-history
4
- sub: clear
5
- description: Manually delete the persistent chat-history log — asks for confirmation, optionally archives to a timestamped backup before wiping
6
- disable-model-invocation: true
7
- suggestion:
8
- eligible: false
9
- rationale: "Destructive log wipe — must be deliberate."
10
- ---
11
- <!-- cloud_safe: noop -->
12
-
13
- # /chat-history clear
14
- Wipes `.agent-chat-history`. Use when the log is stale (wrong session),
15
- bloated beyond usefulness, or contains information you do not want
16
- persisted on disk.
17
-
18
- This command is **destructive** — always asks for confirmation before
19
- touching the file, unless the file does not exist in the first place.
20
-
21
- ## When NOT to use
22
-
23
- - Inspect before deleting → [`/chat-history`](chat-history.md).
24
- - Keep the entries but re-point the header → [`/chat-history-resume`](chat-history-resume.md).
25
- - Disable logging entirely → set `chat_history.enabled: false` in
26
- `.agent-settings.yml`; see
27
- [`layered-settings`](../../docs/guidelines/agent-infra/layered-settings.md#section-aware-merge-rules).
28
- Disabling does not delete the existing file; run this command
29
- afterwards if you also want it gone.
30
-
31
- ## Steps
32
-
33
- ### 1. Check current state
34
-
35
- Run `scripts/chat_history.py status`. If `exists: false`, tell the user
36
- and stop:
37
-
38
- ```
39
- > 📒 No .agent-chat-history to clear.
40
- ```
41
-
42
- ### 2. Show what is about to be deleted
43
-
44
- Render a short preview so the user sees what they are wiping:
45
-
46
- ```
47
- > 📒 About to clear .agent-chat-history
48
- >
49
- > Size: {size_kb} KB
50
- > Entries: {entries}
51
- > Session: {short_fp} (started {created_at_relative})
52
- >
53
- > 1. Archive — rename to .agent-chat-history.{YYYYMMDD-HHMMSS}.bak, then start fresh
54
- > 2. Delete — permanent, no backup
55
- > 3. Cancel — keep the file as-is
56
- ```
57
-
58
- ### 3. Act on the choice
59
-
60
- - `1` (Archive) → `mv .agent-chat-history
61
- .agent-chat-history.{timestamp}.bak`. The rule will create a fresh
62
- file on the next append.
63
- - `2` (Delete) → run `scripts/chat_history.py clear`. Permanent.
64
- - `3` (Cancel) → stop. Make no changes.
65
-
66
- Free-text replies ("abbrechen", "keep it", "nevermind") count as `3`.
67
- An unrecognized reply also counts as `3` — never delete on ambiguous
68
- input.
69
-
70
- ### 4. Confirm
71
-
72
- After a successful archive or delete, print a one-line confirmation:
73
-
74
- ```
75
- > 📒 Archived to .agent-chat-history.{timestamp}.bak
76
- ```
77
-
78
- or
79
-
80
- ```
81
- > 📒 .agent-chat-history deleted.
82
- ```
83
-
84
- Do **not** re-enable logging or change `.agent-settings.yml` as a side
85
- effect — this command is scoped to the file on disk only.
86
-
87
- ## Gotchas
88
-
89
- - `.agent-chat-history.*.bak` files are also git-ignored by the
90
- installer's `.gitignore` block. They accumulate if archived often —
91
- users can delete them manually.
92
- - If `chat_history.enabled: false`, the file will **not** be recreated
93
- after clearing. That is usually fine, but mention it so the user
94
- knows the log is now silent.
95
- - Deletion cannot be undone. When in doubt, prefer option `1` (Archive).
96
-
97
- ## See also
98
-
99
- - [`chat-history`](../rules/chat-history-ownership.md) — the rule that writes the file
100
- - [`/chat-history`](chat-history.md) — status inspection
101
- - [`/chat-history-resume`](chat-history-resume.md) — load + adopt instead of wipe
102
- - [`agent-settings` template](../templates/agent-settings.md) — `chat_history.*` reference
103
- - [`scripts/chat_history.py`](../../../scripts/chat_history.py) — helper API