claude-dev-env 8.85.1 → 8.85.2

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 (30) hide show
  1. package/.agents/skills/efficient-review/SKILL.md +79 -0
  2. package/.agents/skills/efficient-review/defaults.json +12 -0
  3. package/.agents/skills/efficient-review/reference/cloud-session.md +13 -0
  4. package/bin/codex-trimmed-sol.test.mjs +1 -0
  5. package/bin/ever-shipped-skills.mjs +1 -0
  6. package/bin/install-deepstack-plugin.mjs +300 -0
  7. package/bin/install-deepstack-plugin.test.mjs +98 -0
  8. package/bin/install-pstack-plugin.mjs +3 -5
  9. package/bin/install-pstack-plugin.test.mjs +28 -0
  10. package/bin/install.agents-home.test.mjs +1 -0
  11. package/bin/install.codex-rules.test.mjs +2 -0
  12. package/bin/install.cursor-rules.test.mjs +1 -0
  13. package/bin/install.mjs +24 -0
  14. package/bin/install.profile-settings.test.mjs +1 -0
  15. package/bin/install.prune.test.mjs +20 -0
  16. package/bin/install.question-presentation.test.mjs +4 -0
  17. package/bin/install.settings-defaults.test.mjs +1 -0
  18. package/bin/install.shared-settings.test.mjs +1 -0
  19. package/bin/install.test.mjs +51 -1
  20. package/bin/install.transaction.test.mjs +8 -0
  21. package/bin/install.uninstall-transaction.test.mjs +2 -0
  22. package/bin/install.update-keeps-hooks.test.mjs +1 -0
  23. package/docs/decisions/deepstack-install.md +9 -0
  24. package/docs/rule-guides/deepstack-wording.md +23 -0
  25. package/package.json +1 -1
  26. package/rules/deepstack-wording.md +15 -0
  27. package/scripts/deepstack_inventory.py +348 -0
  28. package/scripts/dev_env_scripts_constants/deepstack_inventory_constants.py +42 -0
  29. package/scripts/test_deepstack_inventory.py +137 -0
  30. package/scripts/test_deepstack_wording_rule.py +20 -0
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env python3
2
+ """Diff the DeepStack retirement manifest against the active tree.
3
+
4
+ Each row names a retired path, the behavior it required, the trigger that
5
+ covers it, and the requirement that stays. The eval row keeps a
6
+ reproducible check. An instruction or a manifest cell that marks evals
7
+ optional, or that drops that check, is a finding.
8
+
9
+ This script reads the manifest and the active files. It does not rewrite them.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import sys
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+
19
+ from active_capability_references import unresolved_active_capabilities
20
+ from dev_env_scripts_constants.deepstack_inventory_constants import (
21
+ ALL_EVAL_OPTIONAL_MARKERS,
22
+ ALL_TRUST_CARD_LINES,
23
+ DEEPSTACK_SECTION_HEADING,
24
+ DEEPSTACK_SKILL_RELATIVE_PATH,
25
+ EVAL_GUIDE_PATH_SUFFIX,
26
+ FINDING_BEHAVIOR_STILL_ACTIVE,
27
+ FINDING_DROPS_FIRST_ACTION_NOTE,
28
+ FINDING_DROPS_REPRODUCIBLE_CHECK,
29
+ FINDING_DROPS_TRUST_LINE,
30
+ FINDING_MARKS_EVALS_OPTIONAL,
31
+ FINDING_TRIGGER_MISSING,
32
+ FIRST_ACTION_BEHAVIOR_PREFIX,
33
+ FIRST_ACTION_RETIRED_SENTENCE,
34
+ MANIFEST_RELATIVE_PATH,
35
+ PATH_NONE,
36
+ REPRODUCIBLE_CHECK_SENTENCE,
37
+ RETIRED_PATH_HEADER,
38
+ SHORTEST_BACKTICKED_CELL,
39
+ TABLE_COLUMN_COUNT,
40
+ UTF8_ENCODING,
41
+ )
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class ReplacementRow:
46
+ """One retired behavior and the trigger that covers it."""
47
+
48
+ retired_path: str
49
+ behavior: str
50
+ trigger: str
51
+ requirement_that_stays: str
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class InventoryFinding:
56
+ """One place the active tree still carries a retired behavior."""
57
+
58
+ retired_path: str
59
+ code: str
60
+ detail: str
61
+
62
+
63
+ def _unquote(cell: str) -> str:
64
+ text = cell.strip()
65
+ if len(text) >= SHORTEST_BACKTICKED_CELL and text.startswith("`") and text.endswith("`"):
66
+ return text[1:-1]
67
+ return text
68
+
69
+
70
+ def _table_cells(line: str) -> tuple[str, ...] | None:
71
+ stripped = line.strip()
72
+ if not stripped.startswith("|"):
73
+ return None
74
+ cells = [_unquote(each_cell) for each_cell in stripped.strip("|").split("|")]
75
+ if len(cells) != TABLE_COLUMN_COUNT:
76
+ return None
77
+ if cells[0].startswith("-") or cells[0].lower() == RETIRED_PATH_HEADER:
78
+ return None
79
+ return tuple(cells)
80
+
81
+
82
+ def _row_from_line(each_line: str) -> ReplacementRow | None:
83
+ cells = _table_cells(each_line)
84
+ if cells is None:
85
+ return None
86
+ return ReplacementRow(
87
+ retired_path=cells[0],
88
+ behavior=cells[1],
89
+ trigger=cells[2],
90
+ requirement_that_stays=cells[3],
91
+ )
92
+
93
+
94
+ def replacement_rows(manifest_text: str) -> tuple[ReplacementRow, ...]:
95
+ """Return the rows under the DeepStack heading, or none when it is absent.
96
+
97
+ Args:
98
+ manifest_text: The archive manifest text.
99
+
100
+ Returns:
101
+ One row per table line under the heading.
102
+ """
103
+ all_lines = manifest_text.splitlines()
104
+ is_in_section = False
105
+ all_rows: list[ReplacementRow] = []
106
+ for each_line in all_lines:
107
+ if each_line.startswith(DEEPSTACK_SECTION_HEADING):
108
+ is_in_section = True
109
+ continue
110
+ if is_in_section and each_line.startswith("## "):
111
+ break
112
+ if not is_in_section:
113
+ continue
114
+ each_row = _row_from_line(each_line)
115
+ if each_row is None:
116
+ continue
117
+ all_rows.append(each_row)
118
+ return tuple(all_rows)
119
+
120
+
121
+ def _is_eval_retirement(row: ReplacementRow) -> bool:
122
+ return row.retired_path.endswith(EVAL_GUIDE_PATH_SUFFIX) or row.behavior.startswith(
123
+ FIRST_ACTION_BEHAVIOR_PREFIX
124
+ )
125
+
126
+
127
+ def _optional_eval_finding(
128
+ each_text: str, each_surface: str, retired_path: str
129
+ ) -> InventoryFinding | None:
130
+ """Return a finding when the text marks evals optional."""
131
+ lowered = each_text.lower()
132
+ if any(each_marker in lowered for each_marker in ALL_EVAL_OPTIONAL_MARKERS):
133
+ return InventoryFinding(
134
+ retired_path=retired_path,
135
+ code=FINDING_MARKS_EVALS_OPTIONAL,
136
+ detail=each_surface,
137
+ )
138
+ return None
139
+
140
+
141
+ def _dropped_check_finding(
142
+ each_text: str, each_surface: str, retired_path: str
143
+ ) -> InventoryFinding | None:
144
+ """Return a finding when the reproducible-check sentence is absent."""
145
+ if REPRODUCIBLE_CHECK_SENTENCE in each_text:
146
+ return None
147
+ return InventoryFinding(
148
+ retired_path=retired_path,
149
+ code=FINDING_DROPS_REPRODUCIBLE_CHECK,
150
+ detail=each_surface,
151
+ )
152
+
153
+
154
+ def _trust_line_findings(
155
+ each_text: str, each_surface: str, retired_path: str
156
+ ) -> tuple[InventoryFinding, ...]:
157
+ """Return one finding per trust-card line the text omits."""
158
+ all_findings: list[InventoryFinding] = []
159
+ for each_trust_line in ALL_TRUST_CARD_LINES:
160
+ if each_trust_line not in each_text:
161
+ all_findings.append(
162
+ InventoryFinding(
163
+ retired_path=retired_path,
164
+ code=FINDING_DROPS_TRUST_LINE,
165
+ detail=f"{each_surface}:{each_trust_line}",
166
+ )
167
+ )
168
+ return tuple(all_findings)
169
+
170
+
171
+ def _surface_findings(
172
+ each_text: str, each_surface: str, retired_path: str
173
+ ) -> tuple[InventoryFinding, ...]:
174
+ """Return the optional-eval, dropped-check, and trust-line findings for one surface."""
175
+ all_findings: list[InventoryFinding] = []
176
+ optional_eval = _optional_eval_finding(each_text, each_surface, retired_path)
177
+ if optional_eval is not None:
178
+ all_findings.append(optional_eval)
179
+ dropped_check = _dropped_check_finding(each_text, each_surface, retired_path)
180
+ if dropped_check is not None:
181
+ all_findings.append(dropped_check)
182
+ all_findings.extend(_trust_line_findings(each_text, each_surface, retired_path))
183
+ return tuple(all_findings)
184
+
185
+
186
+ def eval_retirement_findings(
187
+ instruction_text: str, requirement_text: str, retired_path: str
188
+ ) -> tuple[InventoryFinding, ...]:
189
+ """Reject an eval retirement that makes the reproducible check optional.
190
+
191
+ Args:
192
+ instruction_text: The active instruction the row points at.
193
+ requirement_text: The manifest cell for the requirement that stays.
194
+ retired_path: Path named by the row, used on each finding.
195
+
196
+ Returns:
197
+ Findings for an optional eval or a dropped reproducible check.
198
+ """
199
+ all_findings: list[InventoryFinding] = []
200
+ for each_text, each_surface in (
201
+ (instruction_text, "instruction"),
202
+ (requirement_text, "manifest"),
203
+ ):
204
+ all_findings.extend(_surface_findings(each_text, each_surface, retired_path))
205
+ if FIRST_ACTION_RETIRED_SENTENCE not in requirement_text:
206
+ all_findings.append(
207
+ InventoryFinding(
208
+ retired_path=retired_path,
209
+ code=FINDING_DROPS_FIRST_ACTION_NOTE,
210
+ detail="manifest",
211
+ )
212
+ )
213
+ return tuple(all_findings)
214
+
215
+
216
+ def _read_text(path: Path) -> str:
217
+ if not path.is_file():
218
+ return ""
219
+ return path.read_text(encoding=UTF8_ENCODING)
220
+
221
+
222
+ def _instruction_text(package_root: Path, retired_path: str) -> str:
223
+ """Return the active file for a retired path, or empty text for the none row."""
224
+ if retired_path == PATH_NONE:
225
+ return ""
226
+ return _read_text(package_root / retired_path)
227
+
228
+
229
+ def _missing_trigger_finding(
230
+ each_row: ReplacementRow, skill_text: str
231
+ ) -> InventoryFinding | None:
232
+ if each_row.trigger in skill_text:
233
+ return None
234
+ return InventoryFinding(
235
+ retired_path=each_row.retired_path,
236
+ code=FINDING_TRIGGER_MISSING,
237
+ detail=each_row.trigger,
238
+ )
239
+
240
+
241
+ def _live_behavior_finding(
242
+ each_row: ReplacementRow, instruction_text: str
243
+ ) -> InventoryFinding | None:
244
+ if not each_row.behavior or each_row.behavior not in instruction_text:
245
+ return None
246
+ return InventoryFinding(
247
+ retired_path=each_row.retired_path,
248
+ code=FINDING_BEHAVIOR_STILL_ACTIVE,
249
+ detail=each_row.behavior,
250
+ )
251
+
252
+
253
+ def _row_findings(
254
+ package_root: Path, each_row: ReplacementRow, skill_text: str
255
+ ) -> tuple[InventoryFinding, ...]:
256
+ """Return the trigger, behavior, and eval findings for one manifest row."""
257
+ instruction_text = _instruction_text(package_root, each_row.retired_path)
258
+ all_findings: list[InventoryFinding] = []
259
+ missing_trigger = _missing_trigger_finding(each_row, skill_text)
260
+ if missing_trigger is not None:
261
+ all_findings.append(missing_trigger)
262
+ live_behavior = _live_behavior_finding(each_row, instruction_text)
263
+ if live_behavior is not None:
264
+ all_findings.append(live_behavior)
265
+ if _is_eval_retirement(each_row):
266
+ all_findings.extend(
267
+ eval_retirement_findings(
268
+ instruction_text,
269
+ each_row.requirement_that_stays,
270
+ each_row.retired_path,
271
+ )
272
+ )
273
+ return tuple(all_findings)
274
+
275
+
276
+ def findings_for_rows(
277
+ package_root: Path,
278
+ all_rows: tuple[ReplacementRow, ...],
279
+ skill_text: str,
280
+ ) -> tuple[InventoryFinding, ...]:
281
+ """Return findings for retired behaviors that the active tree still requires.
282
+
283
+ Args:
284
+ package_root: Package directory that holds the active files.
285
+ all_rows: Manifest rows under the DeepStack heading.
286
+ skill_text: Install note that must name each trigger.
287
+
288
+ Returns:
289
+ Findings for missing triggers, live behaviors, and eval rows.
290
+ """
291
+ all_findings: list[InventoryFinding] = []
292
+ for each_row in all_rows:
293
+ all_findings.extend(_row_findings(package_root, each_row, skill_text))
294
+ return tuple(all_findings)
295
+
296
+
297
+ def inventory_findings(package_root: Path) -> tuple[InventoryFinding, ...]:
298
+ """Return DeepStack findings plus leftover retired capability names.
299
+
300
+ Args:
301
+ package_root: Package directory that holds the manifest and the active files.
302
+
303
+ Returns:
304
+ Findings from the manifest rows and from leftover retired capability names.
305
+ """
306
+ manifest_text = _read_text(package_root / MANIFEST_RELATIVE_PATH)
307
+ all_rows = replacement_rows(manifest_text)
308
+ skill_text = _read_text(package_root / DEEPSTACK_SKILL_RELATIVE_PATH)
309
+ all_findings = list(findings_for_rows(package_root, all_rows, skill_text))
310
+ for each_reference in unresolved_active_capabilities(package_root):
311
+ all_findings.append(
312
+ InventoryFinding(
313
+ retired_path=each_reference.file_path,
314
+ code="retired-capability",
315
+ detail=each_reference.capability_name,
316
+ )
317
+ )
318
+ return tuple(all_findings)
319
+
320
+
321
+ def _format_finding(finding: InventoryFinding) -> str:
322
+ return f"{finding.retired_path}: {finding.code}: {finding.detail}"
323
+
324
+
325
+ def main(all_arguments: list[str]) -> int:
326
+ """Print each finding and return 1 when the active tree fails the manifest.
327
+
328
+ Args:
329
+ all_arguments: Command-line arguments, without the program name.
330
+
331
+ Returns:
332
+ 0 when the tree has no findings, and 1 when it has any.
333
+ """
334
+ parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
335
+ parser.add_argument(
336
+ "--package-root",
337
+ type=Path,
338
+ default=Path(__file__).resolve().parent.parent,
339
+ )
340
+ arguments = parser.parse_args(all_arguments)
341
+ all_findings = inventory_findings(arguments.package_root)
342
+ for each_finding in all_findings:
343
+ sys.stdout.write(_format_finding(each_finding) + "\n")
344
+ return 1 if all_findings else 0
345
+
346
+
347
+ if __name__ == "__main__":
348
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,42 @@
1
+ """Constants for the DeepStack retirement inventory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ UTF8_ENCODING: str = "utf-8"
6
+ SHORTEST_BACKTICKED_CELL: int = 2
7
+ TABLE_COLUMN_COUNT: int = 4
8
+ MANIFEST_RELATIVE_PATH: str = "rules-archived/ARCHIVE-MANIFEST.md"
9
+ DEEPSTACK_SECTION_HEADING: str = "## DeepStack replacements"
10
+ DEEPSTACK_SKILL_RELATIVE_PATH: str = "docs/decisions/deepstack-install.md"
11
+ EVAL_GUIDE_PATH_SUFFIX: str = "features-start-with-an-eval.md"
12
+ FIRST_ACTION_BEHAVIOR_PREFIX: str = "Your first action is the Skill tool"
13
+ RETIRED_PATH_HEADER: str = "retired path"
14
+ PATH_NONE: str = "none"
15
+ REQUIREMENT_NONE: str = "none"
16
+ REPRODUCIBLE_CHECK_SENTENCE: str = (
17
+ "A reproducible, re-runnable check is still required before any done claim."
18
+ )
19
+ FIRST_ACTION_RETIRED_SENTENCE: str = (
20
+ "Only the forced position as the very first action is retired."
21
+ )
22
+ ALL_TRUST_CARD_LINES: tuple[str, ...] = (
23
+ "Red before the fix",
24
+ "wrong twin fails",
25
+ "undo fails again",
26
+ )
27
+ ALL_EVAL_OPTIONAL_MARKERS: tuple[str, ...] = (
28
+ "evals are optional",
29
+ "eval is optional",
30
+ "evals optional",
31
+ "optional eval",
32
+ "reproducible check is removed",
33
+ "reproducible check is retired",
34
+ "no reproducible check",
35
+ "without a reproducible check",
36
+ )
37
+ FINDING_BEHAVIOR_STILL_ACTIVE: str = "behavior-still-active"
38
+ FINDING_TRIGGER_MISSING: str = "trigger-missing"
39
+ FINDING_MARKS_EVALS_OPTIONAL: str = "marks-evals-optional"
40
+ FINDING_DROPS_REPRODUCIBLE_CHECK: str = "drops-reproducible-check"
41
+ FINDING_DROPS_TRUST_LINE: str = "drops-trust-line"
42
+ FINDING_DROPS_FIRST_ACTION_NOTE: str = "drops-first-action-retired-note"
@@ -0,0 +1,137 @@
1
+ """The inventory rejects an eval retirement that drops the reproducible check."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ import textwrap
8
+ from pathlib import Path
9
+
10
+ from deepstack_inventory import (
11
+ findings_for_rows,
12
+ inventory_findings,
13
+ replacement_rows,
14
+ eval_retirement_findings,
15
+ )
16
+ from dev_env_scripts_constants.deepstack_inventory_constants import (
17
+ FINDING_BEHAVIOR_STILL_ACTIVE,
18
+ FINDING_DROPS_REPRODUCIBLE_CHECK,
19
+ FINDING_MARKS_EVALS_OPTIONAL,
20
+ REPRODUCIBLE_CHECK_SENTENCE,
21
+ )
22
+
23
+ _PACKAGE_ROOT = Path(__file__).resolve().parent.parent
24
+ _KEPT_REQUIREMENT = (
25
+ f"{REPRODUCIBLE_CHECK_SENTENCE} "
26
+ "Red before the fix, the wrong twin fails, and undo fails again. "
27
+ "Only the forced position as the very first action is retired."
28
+ )
29
+ _KEPT_INSTRUCTION = (
30
+ "The DeepStack reproducible-check leaf calls this guide after intake. "
31
+ f"{_KEPT_REQUIREMENT}"
32
+ )
33
+ _FIRST_ACTION = (
34
+ "Your first action is the Skill tool with skill `claude-api` and args `build-eval`"
35
+ )
36
+
37
+
38
+ def _row_block(requirement: str, instruction_phrase: str) -> str:
39
+ return textwrap.dedent(
40
+ f"""\
41
+ ## DeepStack replacements
42
+
43
+ | Retired path | Behavior it required | Trigger that replaces it | Requirement that stays |
44
+ |---|---|---|---|
45
+ | docs/rule-guides/features-start-with-an-eval.md | {instruction_phrase} | DeepStack reproducible-check leaf | {requirement} |
46
+ """
47
+ )
48
+
49
+
50
+ def test_should_reject_an_instruction_that_marks_evals_optional() -> None:
51
+ findings = eval_retirement_findings(
52
+ "Evals are optional before a done claim.",
53
+ "Evals are optional. The reproducible check is removed.",
54
+ "docs/rule-guides/features-start-with-an-eval.md",
55
+ )
56
+ assert any(each.code == FINDING_MARKS_EVALS_OPTIONAL for each in findings)
57
+ assert any(each.code == FINDING_DROPS_REPRODUCIBLE_CHECK for each in findings)
58
+
59
+
60
+ def test_should_reject_a_manifest_that_removes_the_reproducible_check() -> None:
61
+ findings = eval_retirement_findings(
62
+ _KEPT_INSTRUCTION,
63
+ "Only the forced position as the very first action is retired.",
64
+ "docs/rule-guides/features-start-with-an-eval.md",
65
+ )
66
+ manifest_drops = [
67
+ each
68
+ for each in findings
69
+ if each.code == FINDING_DROPS_REPRODUCIBLE_CHECK and each.detail == "manifest"
70
+ ]
71
+ assert manifest_drops
72
+
73
+
74
+ def test_should_accept_a_retirement_that_keeps_the_reproducible_check() -> None:
75
+ assert eval_retirement_findings(
76
+ _KEPT_INSTRUCTION,
77
+ _KEPT_REQUIREMENT,
78
+ "docs/rule-guides/features-start-with-an-eval.md",
79
+ ) == ()
80
+
81
+
82
+ def test_should_reject_a_tree_whose_guide_still_orders_the_first_action(tmp_path: Path) -> None:
83
+ package_root = tmp_path
84
+ guide = package_root / "docs" / "rule-guides" / "features-start-with-an-eval.md"
85
+ guide.parent.mkdir(parents=True)
86
+ guide.write_text(f"{_FIRST_ACTION}. Build the feature.\n", encoding="utf-8")
87
+ skill = package_root / "docs" / "decisions" / "deepstack-install.md"
88
+ skill.parent.mkdir(parents=True)
89
+ skill.write_text("DeepStack reproducible-check leaf\n", encoding="utf-8")
90
+ manifest = package_root / "rules-archived" / "ARCHIVE-MANIFEST.md"
91
+ manifest.parent.mkdir(parents=True)
92
+ manifest.write_text(_row_block(_KEPT_REQUIREMENT, _FIRST_ACTION), encoding="utf-8")
93
+ all_rows = replacement_rows(manifest.read_text(encoding="utf-8"))
94
+ findings = findings_for_rows(package_root, all_rows, skill.read_text(encoding="utf-8"))
95
+ assert any(each.code == FINDING_BEHAVIOR_STILL_ACTIVE for each in findings)
96
+ assert any(each.code == FINDING_DROPS_REPRODUCIBLE_CHECK for each in findings)
97
+
98
+
99
+ def test_should_accept_a_tree_that_retires_only_the_first_action(tmp_path: Path) -> None:
100
+ package_root = tmp_path
101
+ guide = package_root / "docs" / "rule-guides" / "features-start-with-an-eval.md"
102
+ guide.parent.mkdir(parents=True)
103
+ guide.write_text(_KEPT_INSTRUCTION + "\n", encoding="utf-8")
104
+ skill = package_root / "docs" / "decisions" / "deepstack-install.md"
105
+ skill.parent.mkdir(parents=True)
106
+ skill.write_text("DeepStack reproducible-check leaf\n", encoding="utf-8")
107
+ manifest = package_root / "rules-archived" / "ARCHIVE-MANIFEST.md"
108
+ manifest.parent.mkdir(parents=True)
109
+ manifest.write_text(_row_block(_KEPT_REQUIREMENT, _FIRST_ACTION), encoding="utf-8")
110
+ (package_root / ".agents" / "skills" / "kept").mkdir(parents=True)
111
+ (package_root / ".agents" / "skills" / "kept" / "SKILL.md").write_text(
112
+ "---\nname: kept\n---\n", encoding="utf-8"
113
+ )
114
+ (package_root / "bin").mkdir()
115
+ (package_root / "bin" / "ever-shipped-skills.mjs").write_text("'kept'\n", encoding="utf-8")
116
+ assert inventory_findings(package_root) == ()
117
+
118
+
119
+ def test_install_citation_names_the_plugin_repository_and_the_triggers() -> None:
120
+ text = (_PACKAGE_ROOT / "docs" / "decisions" / "deepstack-install.md").read_text(encoding="utf-8")
121
+ assert "jl-cmd-projects/DeepStack" in text
122
+ assert "deepstack@DeepStack" in text
123
+ assert "DeepStack intake" in text
124
+ assert "DeepStack search leaf" in text
125
+ assert "DeepStack reproducible-check leaf" in text
126
+
127
+
128
+ def test_live_package_inventory_exits_clean() -> None:
129
+ completed = subprocess.run(
130
+ [sys.executable, str(_PACKAGE_ROOT / "scripts" / "deepstack_inventory.py")],
131
+ capture_output=True,
132
+ text=True,
133
+ check=False,
134
+ cwd=str(_PACKAGE_ROOT),
135
+ )
136
+ assert completed.returncode == 0
137
+ assert completed.stdout == ""
@@ -0,0 +1,20 @@
1
+ """The wording rule cites the first-task gate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ _PACKAGE_ROOT = Path(__file__).resolve().parent.parent
8
+
9
+
10
+ def test_wording_rule_cites_the_gate() -> None:
11
+ rule_text = (_PACKAGE_ROOT / "rules" / "deepstack-wording.md").read_text(encoding="utf-8")
12
+ guide_text = (_PACKAGE_ROOT / "docs" / "rule-guides" / "deepstack-wording.md").read_text(
13
+ encoding="utf-8"
14
+ )
15
+ assert "hooks/session/deepen_first_prompt.py" in rule_text
16
+ assert "hooks/session/deepen_first_prompt.py" in guide_text
17
+ assert "SCOPE (role):" in guide_text
18
+ assert "EXAMPLES (illustration only):" in guide_text
19
+ assert "ZOOM-IN BLOCKER:" in guide_text
20
+ assert "DeepStack" in rule_text