codexspec 0.7.6__tar.gz → 0.7.7__tar.gz

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 (65) hide show
  1. {codexspec-0.7.6 → codexspec-0.7.7}/PKG-INFO +9 -1
  2. {codexspec-0.7.6 → codexspec-0.7.7}/README.md +8 -0
  3. {codexspec-0.7.6 → codexspec-0.7.7}/pyproject.toml +1 -1
  4. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/__init__.py +150 -10
  5. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/commands/installer.py +16 -2
  6. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/integrations/codex.py +2 -0
  7. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/commit-staged.md +10 -0
  8. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/config.md +24 -0
  9. codexspec-0.7.7/templates/commands/distill.md +110 -0
  10. codexspec-0.7.7/templates/commands/evolve.md +74 -0
  11. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/implement-tasks.md +10 -0
  12. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/pr.md +10 -0
  13. {codexspec-0.7.6 → codexspec-0.7.7}/.gitignore +0 -0
  14. {codexspec-0.7.6 → codexspec-0.7.7}/LICENSE +0 -0
  15. {codexspec-0.7.6 → codexspec-0.7.7}/codexspec-icon.svg +0 -0
  16. {codexspec-0.7.6 → codexspec-0.7.7}/codexspec-logo-dark.svg +0 -0
  17. {codexspec-0.7.6 → codexspec-0.7.7}/codexspec-logo-light.svg +0 -0
  18. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/check-i18n-completeness.sh +0 -0
  19. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/check-i18n-structure.sh +0 -0
  20. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/check-prerequisites.sh +0 -0
  21. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/common.sh +0 -0
  22. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/create-new-feature.sh +0 -0
  23. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/bash/review-context.sh +0 -0
  24. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/powershell/check-prerequisites.ps1 +0 -0
  25. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/powershell/common.ps1 +0 -0
  26. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/powershell/create-new-feature.ps1 +0 -0
  27. {codexspec-0.7.6 → codexspec-0.7.7}/scripts/powershell/review-context.ps1 +0 -0
  28. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/commands/__init__.py +0 -0
  29. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/i18n.py +0 -0
  30. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/idea.md +0 -0
  31. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/integrations/__init__.py +0 -0
  32. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/integrations/base.py +0 -0
  33. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/integrations/claude.py +0 -0
  34. {codexspec-0.7.6 → codexspec-0.7.7}/src/codexspec/translator.py +0 -0
  35. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/analyze.md +0 -0
  36. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/checklist.md +0 -0
  37. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/clarify.md +0 -0
  38. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/constitution.md +0 -0
  39. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/generate-spec.md +0 -0
  40. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/plan-to-tasks.md +0 -0
  41. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/quick.md +0 -0
  42. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/review-code.md +0 -0
  43. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/review-plan.md +0 -0
  44. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/review-spec.md +0 -0
  45. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/review-tasks.md +0 -0
  46. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/spec-to-plan.md +0 -0
  47. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/specify.md +0 -0
  48. {codexspec-0.7.6 → codexspec-0.7.7}/templates/commands/tasks-to-issues.md +0 -0
  49. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/checklist-template.md +0 -0
  50. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/constitution-template.md +0 -0
  51. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/plan-template-detailed.md +0 -0
  52. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/plan-template-simple.md +0 -0
  53. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/requirements-template.md +0 -0
  54. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/spec-template-detailed.md +0 -0
  55. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/spec-template-simple.md +0 -0
  56. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/tasks-template-detailed.md +0 -0
  57. {codexspec-0.7.6 → codexspec-0.7.7}/templates/docs/tasks-template-simple.md +0 -0
  58. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/de.json +0 -0
  59. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/en.json +0 -0
  60. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/es.json +0 -0
  61. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/fr.json +0 -0
  62. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/ja.json +0 -0
  63. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/ko.json +0 -0
  64. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/pt-BR.json +0 -0
  65. {codexspec-0.7.6 → codexspec-0.7.7}/templates/translations/zh-CN.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codexspec
3
- Version: 0.7.6
3
+ Version: 0.7.7
4
4
  Summary: CodexSpec - A Requirements-First SDD toolkit for Claude Code
5
5
  Project-URL: Homepage, https://github.com/Zts0hg/codexspec
6
6
  Project-URL: Repository, https://github.com/Zts0hg/codexspec
@@ -555,6 +555,7 @@ Implementation follows **conditional TDD workflow**:
555
555
  | `--set-commit-lang`, `-c` | Set the commit-message language |
556
556
  | `--list-langs` | List all supported languages |
557
557
  | `--auto-next` | Toggle/set `workflow.auto_next` (bare toggles; or on/off) |
558
+ | `--auto-distill` | Toggle/set `workflow.auto_distill` (default on; bare toggles; or on/off) |
558
559
 
559
560
  </details>
560
561
 
@@ -589,6 +590,13 @@ Implementation follows **conditional TDD workflow**:
589
590
  | `/codexspec:checklist` | Generate requirements quality checklist |
590
591
  | `/codexspec:tasks-to-issues` | Convert tasks to GitHub Issues |
591
592
 
593
+ #### Self-Evolution Commands
594
+
595
+ | Command | Description |
596
+ | --- | --- |
597
+ | `/codexspec:distill` | Distill reusable cross-feature knowledge into `.codexspec/profile/` |
598
+ | `/codexspec:evolve` | Compile profile knowledge into a command/skill and contribute upstream via a reviewed PR |
599
+
592
600
  #### Git Workflow Commands
593
601
 
594
602
  | Command | Description |
@@ -510,6 +510,7 @@ Implementation follows **conditional TDD workflow**:
510
510
  | `--set-commit-lang`, `-c` | Set the commit-message language |
511
511
  | `--list-langs` | List all supported languages |
512
512
  | `--auto-next` | Toggle/set `workflow.auto_next` (bare toggles; or on/off) |
513
+ | `--auto-distill` | Toggle/set `workflow.auto_distill` (default on; bare toggles; or on/off) |
513
514
 
514
515
  </details>
515
516
 
@@ -544,6 +545,13 @@ Implementation follows **conditional TDD workflow**:
544
545
  | `/codexspec:checklist` | Generate requirements quality checklist |
545
546
  | `/codexspec:tasks-to-issues` | Convert tasks to GitHub Issues |
546
547
 
548
+ #### Self-Evolution Commands
549
+
550
+ | Command | Description |
551
+ | --- | --- |
552
+ | `/codexspec:distill` | Distill reusable cross-feature knowledge into `.codexspec/profile/` |
553
+ | `/codexspec:evolve` | Compile profile knowledge into a command/skill and contribute upstream via a reviewed PR |
554
+
547
555
  #### Git Workflow Commands
548
556
 
549
557
  | Command | Description |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codexspec"
3
- version = "0.7.6"
3
+ version = "0.7.7"
4
4
  description = "CodexSpec - A Requirements-First SDD toolkit for Claude Code"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -43,7 +43,7 @@ from .integrations import get_integrations
43
43
  from .translator import SUPPORTED_LANGUAGES, translate
44
44
 
45
45
  # Version info
46
- __version__ = "0.7.0"
46
+ __version__ = "0.7.7"
47
47
  __author__ = "CodexSpec Team"
48
48
 
49
49
  # Constitution file path constants
@@ -181,6 +181,11 @@ _AUTO_NEXT_FALSE = {"off", "false", "0", "no"}
181
181
  _AUTO_NEXT_SENTINEL = "__toggle__"
182
182
  _AUTO_NEXT_ACCEPTED = "on/off, true/false, 1/0, yes/no"
183
183
 
184
+ # workflow.auto_distill toggle. Unlike auto_next, auto_distill defaults to ON
185
+ # (opt-out): only the literal ``false`` disables it. Reuses the auto_next token
186
+ # sets for parsing; a distinct sentinel keeps the bare-toggle rewrites separate.
187
+ _AUTO_DISTILL_SENTINEL = "__toggle_distill__"
188
+
184
189
 
185
190
  @app.command()
186
191
  def config(
@@ -221,6 +226,13 @@ def config(
221
226
  "--auto-next",
222
227
  help="Toggle workflow.auto_next (bare), or set it (on/off|true/false|1/0|yes/no).",
223
228
  ),
229
+ # ``--auto-distill`` mirrors ``--auto-next``; the bare toggle is rewritten to
230
+ # ``--auto-distill=<sentinel>`` by ``_normalize_auto_distill_argv`` in ``main()``.
231
+ auto_distill: Optional[str] = typer.Option(
232
+ None,
233
+ "--auto-distill",
234
+ help="Toggle workflow.auto_distill (bare), or set it (on/off|true/false|1/0|yes/no).",
235
+ ),
224
236
  ) -> None:
225
237
  """
226
238
  View or modify CodexSpec project configuration.
@@ -233,6 +245,7 @@ def config(
233
245
  codexspec config --set-lang zh-CN # Set language to Chinese
234
246
  codexspec config --set-commit-lang en # Set commit messages to English
235
247
  codexspec config --auto-next # Toggle workflow.auto_next
248
+ codexspec config --auto-distill off # Disable workflow.auto_distill (default on)
236
249
  codexspec config --list-langs # List supported languages
237
250
  """
238
251
  # Handle list languages
@@ -274,6 +287,25 @@ def config(
274
287
  raise typer.Exit(1)
275
288
  return
276
289
 
290
+ # Handle auto-distill toggle/set
291
+ if auto_distill is not None:
292
+ if auto_distill == _AUTO_DISTILL_SENTINEL:
293
+ target = not _read_auto_distill(config_file)
294
+ else:
295
+ try:
296
+ target = parse_auto_distill_value(auto_distill)
297
+ except ValueError:
298
+ console.print(f"[red]Invalid --auto-distill value:[/red] {auto_distill!r}")
299
+ console.print(f"Accepted values: {_AUTO_NEXT_ACCEPTED} (or pass --auto-distill bare to toggle).")
300
+ raise typer.Exit(1)
301
+ if _write_auto_distill(config_file, target):
302
+ state = "enabled" if target else "disabled"
303
+ console.print(f"[green]auto_distill {state}[/green] (workflow.auto_distill = {str(target).lower()})")
304
+ else:
305
+ console.print("[red]Failed to update workflow.auto_distill[/red]")
306
+ raise typer.Exit(1)
307
+ return
308
+
277
309
  # Handle set language
278
310
  if set_lang:
279
311
  normalized = normalize_locale(set_lang)
@@ -1042,6 +1074,101 @@ def _dump_lines(path: Path, lines: list[str]) -> bool:
1042
1074
  return True
1043
1075
 
1044
1076
 
1077
+ # --- workflow.auto_distill toggle helpers ----------------------------------
1078
+
1079
+
1080
+ def parse_auto_distill_value(raw: str) -> bool:
1081
+ """Parse an explicit ``--auto-distill`` value into a boolean.
1082
+
1083
+ Accepts the same tokens as ``--auto-next`` (``on/off``, ``true/false``,
1084
+ ``1/0``, ``yes/no``; case-insensitive, surrounding whitespace ignored).
1085
+ Raises ``ValueError`` for any other token.
1086
+ """
1087
+ token = (raw or "").strip().lower()
1088
+ if token in _AUTO_NEXT_TRUE:
1089
+ return True
1090
+ if token in _AUTO_NEXT_FALSE:
1091
+ return False
1092
+ raise ValueError(f"invalid --auto-distill value: {raw!r}")
1093
+
1094
+
1095
+ def _read_auto_distill(config_file: Path) -> bool:
1096
+ """Return True unless ``workflow.auto_distill`` is the literal ``false``.
1097
+
1098
+ ``auto_distill`` defaults to enabled (opt-out): an absent key/section, an
1099
+ explicit ``true``, or any non-``false`` value enables it; only the literal
1100
+ ``false`` disables it. A missing file also reads as enabled.
1101
+ """
1102
+ try:
1103
+ content = config_file.read_text(encoding="utf-8")
1104
+ except OSError:
1105
+ return True
1106
+ in_workflow = False
1107
+ for line in content.splitlines():
1108
+ if not line.strip():
1109
+ continue
1110
+ if not line[0].isspace(): # top-level key (or comment)
1111
+ key = line.split("#", 1)[0].strip()
1112
+ in_workflow = key == "workflow:"
1113
+ continue
1114
+ if in_workflow:
1115
+ match = re.match(r"^\s*auto_distill:\s*(\S+?)\s*(?:#.*)?$", line)
1116
+ if match:
1117
+ return match.group(1) != "false"
1118
+ return True
1119
+
1120
+
1121
+ def _write_auto_distill(config_file: Path, value: bool) -> bool:
1122
+ """Set ``workflow.auto_distill`` to an unquoted boolean.
1123
+
1124
+ Mirrors ``_write_auto_next``: (1) update the value in place when the key
1125
+ exists under ``workflow:``; (2) insert it as the section's first child when
1126
+ the section exists but the key is absent; (3) append a ``workflow:`` section
1127
+ when absent. Preserves all other lines/comments. Returns ``False`` on I/O
1128
+ error.
1129
+ """
1130
+ try:
1131
+ content = config_file.read_text(encoding="utf-8")
1132
+ except OSError:
1133
+ return False
1134
+
1135
+ token = "true" if value else "false"
1136
+ lines = content.split("\n")
1137
+
1138
+ workflow_idx: Optional[int] = None
1139
+ in_workflow = False
1140
+ for i, line in enumerate(lines):
1141
+ if not line.strip():
1142
+ continue
1143
+ if not line[0].isspace():
1144
+ key = line.split("#", 1)[0].strip()
1145
+ in_workflow = key == "workflow:"
1146
+ if in_workflow:
1147
+ workflow_idx = i
1148
+ continue
1149
+ if in_workflow and re.match(r"^\s*auto_distill:\s*\S+", line):
1150
+ indent = line[: len(line) - len(line.lstrip())]
1151
+ lines[i] = f"{indent}auto_distill: {token}"
1152
+ return _dump_lines(config_file, lines)
1153
+
1154
+ if workflow_idx is not None:
1155
+ lines.insert(workflow_idx + 1, f" auto_distill: {token}")
1156
+ return _dump_lines(config_file, lines)
1157
+
1158
+ section = f"workflow:\n auto_distill: {token}"
1159
+ if not content:
1160
+ new_content = section + "\n"
1161
+ elif content.endswith("\n"):
1162
+ new_content = content + "\n" + section + "\n"
1163
+ else:
1164
+ new_content = content + "\n\n" + section + "\n"
1165
+ try:
1166
+ config_file.write_text(new_content, encoding="utf-8")
1167
+ except OSError:
1168
+ return False
1169
+ return True
1170
+
1171
+
1045
1172
  def _next_step_start(integration_keys: set[str], language: str) -> str:
1046
1173
  """Return a target-aware start instruction."""
1047
1174
  if integration_keys == {"codex"}:
@@ -1439,24 +1566,23 @@ The following slash commands are available in this project:
1439
1566
  """
1440
1567
 
1441
1568
 
1442
- def _normalize_auto_next_argv(argv: list[str]) -> list[str]:
1443
- """Rewrite a bare ``--auto-next`` into ``--auto-next=<sentinel>``.
1569
+ def _normalize_optional_value_argv(argv: list[str], flag: str, sentinel: str) -> list[str]:
1570
+ """Rewrite a bare ``flag`` into ``flag=<sentinel>``.
1444
1571
 
1445
1572
  Click 8.3 no longer honors ``flag_value`` for a bare optional-value option
1446
- (it errors "requires an argument"). To preserve ``codexspec config
1447
- --auto-next`` (bare) as a toggle, a standalone ``--auto-next`` token — one
1448
- that is not already in ``--auto-next=...`` form and is not followed by a
1449
- value token — is rewritten to ``--auto-next=<sentinel>``, which the
1450
- ``config`` handler interprets as a toggle.
1573
+ (it errors "requires an argument"). A standalone ``flag`` token — one that is
1574
+ not already in ``flag=...`` form and is not followed by a value token — is
1575
+ rewritten to ``flag=<sentinel>``, which the ``config`` handler reads as a
1576
+ toggle.
1451
1577
  """
1452
1578
  out: list[str] = []
1453
1579
  i = 0
1454
1580
  while i < len(argv):
1455
1581
  tok = argv[i]
1456
- if tok == "--auto-next":
1582
+ if tok == flag:
1457
1583
  nxt = argv[i + 1] if i + 1 < len(argv) else None
1458
1584
  if nxt is None or nxt.startswith("-"):
1459
- out.append(f"--auto-next={_AUTO_NEXT_SENTINEL}")
1585
+ out.append(f"{flag}={sentinel}")
1460
1586
  else:
1461
1587
  out.append(tok)
1462
1588
  out.append(nxt)
@@ -1467,10 +1593,24 @@ def _normalize_auto_next_argv(argv: list[str]) -> list[str]:
1467
1593
  return out
1468
1594
 
1469
1595
 
1596
+ def _normalize_auto_next_argv(argv: list[str]) -> list[str]:
1597
+ """Rewrite a bare ``--auto-next`` into ``--auto-next=<sentinel>`` (see
1598
+ ``_normalize_optional_value_argv``)."""
1599
+ return _normalize_optional_value_argv(argv, "--auto-next", _AUTO_NEXT_SENTINEL)
1600
+
1601
+
1602
+ def _normalize_auto_distill_argv(argv: list[str]) -> list[str]:
1603
+ """Rewrite a bare ``--auto-distill`` into ``--auto-distill=<sentinel>`` (see
1604
+ ``_normalize_optional_value_argv``)."""
1605
+ return _normalize_optional_value_argv(argv, "--auto-distill", _AUTO_DISTILL_SENTINEL)
1606
+
1607
+
1470
1608
  def main() -> None:
1471
1609
  """Main entry point for the CLI."""
1472
1610
  if "--auto-next" in sys.argv:
1473
1611
  sys.argv = _normalize_auto_next_argv(sys.argv)
1612
+ if "--auto-distill" in sys.argv:
1613
+ sys.argv = _normalize_auto_distill_argv(sys.argv)
1474
1614
  app()
1475
1615
 
1476
1616
 
@@ -47,8 +47,8 @@ def get_commands_metadata() -> list[CommandMetadata]:
47
47
 
48
48
  Returns:
49
49
  List of CommandMetadata dictionaries sorted by category priority:
50
- core (9) -> enhanced (4) -> git (2) -> review (2) -> utility (2)
51
- Total: 19 commands
50
+ core (9) -> enhanced (6) -> git (2) -> review (1) -> utility (2)
51
+ Total: 20 commands
52
52
  """
53
53
  return [
54
54
  # Core Commands (9)
@@ -144,6 +144,20 @@ def get_commands_metadata() -> list[CommandMetadata]:
144
144
  "category": "enhanced",
145
145
  "file_name": "tasks-to-issues.md",
146
146
  },
147
+ {
148
+ "name": "distill",
149
+ "display_name": "/codexspec:distill",
150
+ "description": "从交互中萃取可复用的跨特性知识到 .codexspec/profile/",
151
+ "category": "enhanced",
152
+ "file_name": "distill.md",
153
+ },
154
+ {
155
+ "name": "evolve",
156
+ "display_name": "/codexspec:evolve",
157
+ "description": "将 profile 沉淀编译为命令/技能并通过评审 PR 贡献回上游",
158
+ "category": "enhanced",
159
+ "file_name": "evolve.md",
160
+ },
147
161
  # Git Workflow Commands (2)
148
162
  {
149
163
  "name": "commit-staged",
@@ -113,6 +113,8 @@ Use these Codex skills when working on CodexSpec workflows:
113
113
  - `$codexspec:spec-to-plan` to produce `plan.md`.
114
114
  - `$codexspec:plan-to-tasks` to produce `tasks.md`.
115
115
  - `$codexspec:implement-tasks` to implement approved tasks.
116
+ - `$codexspec:distill` to capture reusable cross-feature knowledge into `.codexspec/profile/`.
117
+ - `$codexspec:evolve` to contribute vetted profile knowledge back upstream via a reviewed PR.
116
118
 
117
119
  Before making workflow decisions, read `.codexspec/memory/constitution.md`.
118
120
  {CODEXSPEC_CONTEXT_END}
@@ -157,3 +157,13 @@ If `git commit` fails due to a pre-commit hook modifying files:
157
157
  - Report the error message to the user
158
158
  - DO NOT attempt to "fix" the situation
159
159
  - The user should investigate and resolve manually
160
+
161
+ ## Automatic Distillation
162
+
163
+ Read `workflow.auto_distill` from `.codexspec/config.yml` (**default `true`** — enabled unless explicitly set to the literal `false`; absent or any non-`false` value means enabled).
164
+
165
+ When `workflow.auto_distill` is enabled (not the literal `false`) AND a commit was created successfully, invoke `/codexspec:distill` exactly once on this session's interaction, then end.
166
+
167
+ - distill is non-blocking and non-interactive: it never prompts and never alters the commit or its message; it early-exits when there is nothing reusable to capture.
168
+ - distill only writes records to `.codexspec/profile/`; it never touches tracked project files or the git state.
169
+ - Do not invoke distill when `auto_distill` is disabled or when no commit was created (for example, an ABORT above).
@@ -107,6 +107,7 @@ Display the configuration as in Step 2, then exit.
107
107
  {"label": "Output language (legacy)", "description": "Fallback language used when interaction/document are not set (currently: {current value})"},
108
108
  {"label": "Commit language", "description": "Language for commit messages (currently: {current value})"},
109
109
  {"label": "Auto-next chain", "description": "Auto-advance the SDD pipeline once a stage passes (workflow.auto_next) (currently: {current value})"},
110
+ {"label": "Auto-distill", "description": "Run /codexspec:distill on completion of wrap-up commands to capture reusable knowledge (workflow.auto_distill) (currently: {current value})"},
110
111
  {"label": "Back", "description": "Return to main menu"}
111
112
  ]
112
113
  }]
@@ -153,6 +154,29 @@ Display the configuration as in Step 2, then exit.
153
154
  a `workflow:` section with `auto_next: <bool>`), preserving every other
154
155
  line and comment.
155
156
 
157
+ 3b. For "Auto-distill", ask whether to enable or disable:
158
+
159
+ ```json
160
+ {
161
+ "questions": [{
162
+ "question": "Set workflow.auto_distill:",
163
+ "header": "Auto-distill",
164
+ "options": [
165
+ {"label": "Enable", "description": "Run /codexspec:distill on completion of wrap-up commands"},
166
+ {"label": "Disable", "description": "Do not auto-distill; run /codexspec:distill manually"},
167
+ {"label": "Back", "description": "Return without changing"}
168
+ ]
169
+ }]
170
+ }
171
+ ```
172
+
173
+ Then read `.codexspec/config.yml`. `auto_distill` is enabled by default; the
174
+ current value is disabled only when `workflow.auto_distill` is the literal
175
+ `false` (an absent key/section, `true`, or any other value is enabled). Write
176
+ `workflow.auto_distill` as an unquoted `true`/`false` (update the value in place
177
+ when the key exists; otherwise add `auto_distill: <bool>` under the `workflow:`
178
+ section, creating that section if absent), preserving every other line and comment.
179
+
156
180
  4. Update the configuration file with the new value
157
181
  5. Display the updated configuration
158
182
  6. Exit
@@ -0,0 +1,110 @@
1
+ ---
2
+ description: Distill reusable, cross-feature knowledge from an interaction into the project profile
3
+ argument-hint: "[interaction segment or context to distill]"
4
+ ---
5
+
6
+ # Distill
7
+
8
+ ## Language Preference
9
+
10
+ Read `.codexspec/config.yml`. Two independent language controls apply (each falls back to `language.output`, then English):
11
+
12
+ - **Interaction language** (`language.interaction`): language for all conversation with the user — questions, explanations, status messages, and `codexspec` CLI terminal output.
13
+ - **Document language** (`language.document`): language for generated artifact files (the profile records).
14
+
15
+ Converse in the interaction language and author artifacts in the document language. Apply the project's translation standard to both: translate by meaning (not word-for-word), keep English for terms with no good native equivalent, and write as if originally in that language. **Exception**: `evidence.facts` quotes the user's original words verbatim and MUST NOT be translated.
16
+
17
+ ## User Input
18
+
19
+ `$ARGUMENTS`
20
+
21
+ ## Operating Model
22
+
23
+ `distill` extracts the reusable, cross-feature knowledge produced during work and persists it to the project-level store `.codexspec/profile/`. It runs two ways:
24
+
25
+ - **Auto (primary)**: embedded in wrap-up commands (`implement-tasks` on completion, `commit-staged`, `pr`), gated by `workflow.auto_distill` in `.codexspec/config.yml` (**default enabled**; disabled only when explicitly set to the literal `false`).
26
+ - **Manual (fallback)**: invoked directly on the supplied or most-recent interaction segment.
27
+
28
+ distill is **non-blocking and non-interactive**: it never prompts, never gates another command, and **early-exits without writing** when the delta contains nothing reusable.
29
+
30
+ **Input contract**: distill operates on "a segment of interaction to distill." It MUST NOT assume it is live in the conversation, so the same routine works whether embedded (fed live context) or invoked manually on a supplied segment.
31
+
32
+ ## What distill captures — and what it must NOT
33
+
34
+ Capture **only** knowledge that is reusable **across features** and that the per-feature SDD artifacts structurally cannot accumulate.
35
+
36
+ Apply this boundary test to every candidate: **"Would a single feature's `requirements.md` / `spec.md` / `plan.md` record this?"**
37
+
38
+ - **Yes** → it is feature-scoped; leave it in that artifact. **Do NOT** copy it into the profile. (Requirement rationale already lives in `requirements.md`; approach rationale in plan/design.)
39
+ - **No / it spans features** → it may enter the profile.
40
+
41
+ **Never** create a feature-local store. The profile is project-level only; a feature's memory is its existing spec directory.
42
+
43
+ ## The profile store: `.codexspec/profile/`
44
+
45
+ Four markdown files, each holding **only current-effective** knowledge — dense, with no "retired" section (git history is the ledger). Create the directory and file on first write.
46
+
47
+ - `constraints.md` — negative constraints (`严禁 / 仅允许`). These carry the **highest** weight and MUST be honored first.
48
+ - `conventions.md` — positive cross-feature conventions / steering.
49
+ - `pitfalls.md` — cross-feature traps and their workarounds.
50
+ - `decisions.md` — cross-feature / architectural decisions only (ADR-lite). **Never** single-feature requirement rationale.
51
+
52
+ ### Record format — `claim` and `evidence` physically separated
53
+
54
+ Every record MUST separate the distilled claim from the evidence it rests on:
55
+
56
+ - `claim` — one-sentence reusable statement.
57
+ - `type` — `convention` | `constraint` | `pitfall` | `decision` (`constraint` = highest priority).
58
+ - `scope/when` — natural-language applicability condition (e.g. "when editing Python code"); omit for global. **No formal syntax.**
59
+ - `evidence.facts` — the concrete observations behind it; **quote the user's original words, do not paraphrase**.
60
+ - `evidence.state` — the context/validity when true (feature / commit / config; still valid?).
61
+ - `provenance` — source feature/session, trigger, timestamp, `derivation = explicit | inferred`.
62
+ - `status` — `vetted` **only** when `derivation = explicit` (the user's own words) AND the item was verified by an outcome (a test passed, a workaround worked); every `inferred` item stays `candidate`. Only `vetted` records are eligible for `evolve`.
63
+
64
+ This separation is what makes a later error locatable as **misread** (facts wrong) vs **overreach** (claim over-generalized) vs **stale** (state no longer holds).
65
+
66
+ Example entry:
67
+
68
+ ```markdown
69
+ ### C-003: Prefer absolute imports
70
+ - claim: Always use absolute imports in `src/`.
71
+ - type: convention
72
+ - scope/when: Python modules under `src/`
73
+ - evidence.facts: "Use absolute imports; relative ones broke the packaged wheel last time."
74
+ - evidence.state: confirmed at feature 2026-0809-2219gg; commit a1b2c3d
75
+ - provenance: distill @implement-tasks, 2026-08-09, derivation: explicit
76
+ - status: vetted
77
+ ```
78
+
79
+ ## Extraction
80
+
81
+ Read the interaction segment and extract, per the dimensions above, only **verified** knowledge — prefer facts confirmed by outcomes over speculation; speculation MUST NOT become `vetted`.
82
+
83
+ Before writing, **read the current profile** and skip anything already covered; update anything changed via `replace`. **This is how deduplication is done — by judgment, not an algorithm.**
84
+
85
+ ## Conflict adjudication
86
+
87
+ When a new item conflicts with an existing rule, resolve in this order:
88
+
89
+ 1. **Recency** — newer corrections win (usually a `replace`).
90
+ 2. **Specificity** — a specific instruction overrides the general one **only within its scope**.
91
+ 3. **Scenario-decoupling** — if neither wins, keep **both** under a `scope/when` condition rather than forcing a winner.
92
+ 4. **Defer, don't guess** — if genuinely unresolvable, write the record with `status: conflict/needs-adjudication` and surface it at the next interactive point or at evolve time. **Never block, never guess.**
93
+
94
+ ## Mutation discipline
95
+
96
+ Change the profile **only** through three conceptual operations (you edit the markdown directly — these are a discipline, **not** a tool API or matching algorithm):
97
+
98
+ - `add` — append a new verified item.
99
+ - `replace` — supersede an outdated/wrong item in place (keeps files dense).
100
+ - `remove` — delete an item invalidated by a changed environment.
101
+
102
+ git history is the audit ledger. Do **NOT** keep a retired section inside the files.
103
+
104
+ ## Vetting candidates (manual, interactive)
105
+
106
+ Auto-distill writes `candidate` records non-interactively and **never prompts**. Promote them through the **manual review mode** — `/distill review` (or `/distill` with no new segment to distill): list every pending `candidate` compactly (claim + evidence + provenance) and let the user approve inline — "vet all", "vet 1,3", "edit 2", "drop 4". Apply the choices by editing each record's `status` (a `replace`). **The user never hand-edits the profile files.**
107
+
108
+ ## Output
109
+
110
+ Report concisely in the interaction language: which records were added / replaced / removed and in which file, any `conflict` records deferred, or "nothing to distill" on early-exit. distill **never** gates the caller.
@@ -0,0 +1,74 @@
1
+ ---
2
+ description: Compile vetted project-profile knowledge into a reusable command/skill and contribute it upstream via a reviewed PR
3
+ argument-hint: "[what to evolve, or a profile area]"
4
+ ---
5
+
6
+ # Evolve
7
+
8
+ ## Language Preference
9
+
10
+ Read `.codexspec/config.yml`. Two independent language controls apply (each falls back to `language.output`, then English):
11
+
12
+ - **Interaction language** (`language.interaction`): language for all conversation with the user — questions, explanations, status messages, and `codexspec` CLI terminal output.
13
+ - **Document language** (`language.document`): language for generated artifact files.
14
+
15
+ Converse in the interaction language. **The compiled command/skill draft is a distributed template and MUST be authored in English** (project i18n convention), regardless of `language.document`. PR title/body follow `language.commit`.
16
+
17
+ ## User Input
18
+
19
+ `$ARGUMENTS`
20
+
21
+ ## Operating Model
22
+
23
+ `evolve` turns **vetted** sediment in `.codexspec/profile/` into a reusable capability and contributes it back to CodexSpec through a **human-reviewed PR**. It **never merges unattended** and **never** edits install artifacts.
24
+
25
+ ## Selecting what to promote
26
+
27
+ Promote only records that are **both**:
28
+
29
+ 1. `status: vetted` (never `candidate` or `conflict`), and
30
+ 2. **general enough for the toolkit** — the generality extension of distill's boundary test: *"Is this useful to every CodexSpec user, or only to this project?"*
31
+
32
+ Project-specific knowledge **stays** in the profile. Only generally-useful capability is promoted upstream. When nothing qualifies, stop and report — do not force a promotion.
33
+
34
+ ## Compiling the draft
35
+
36
+ Compile the selected sediment into a SKILL.md / command-template draft that conforms to both:
37
+
38
+ - **Anthropic Agent Skills** (SKILL.md + progressive disclosure), and
39
+ - **existing CodexSpec command-template conventions** (YAML frontmatter + sections + `## Language Preference`, English).
40
+
41
+ Apply these compile rules:
42
+
43
+ - **Priority order** — core needs first, **negative constraints immediately after (highest weight)**, then the rest.
44
+ - **Logic-clean** — `replace`/`remove` any superseded rule first; the output MUST carry **no** contradictory rules.
45
+ - **Imperative wording** — use **必须 / 始终 / 严禁 / 仅允许** in place of 可以考虑 / 尽量 / 最好不要 / 或许. Match the project's explicit **Prefer / Avoid** rule style; do not use decorative markers.
46
+
47
+ ## Where output goes (self-bootstrap)
48
+
49
+ Write **only** under `templates/` — a new `templates/commands/*.md` or a standalone skill package. **NEVER** edit `.claude/commands/codexspec/`: it is a regenerated install artifact, and any edit there is silently overwritten on the next reinstall and never reaches users. Changes reach users via `publish` → `init`.
50
+
51
+ ## Contribution mechanics
52
+
53
+ **Before any `git push` or PR creation, present the compiled draft file(s) and the value statement to the user and obtain explicit approval. Proceed only on approval; NEVER push or open a PR unattended.** (Writing a local draft under `templates/` is git-reversible; the outward action is what is gated.)
54
+
55
+ On approval, open a PR for human review. **Auto-detect** the git path — this is a mechanics difference only, never a permission tier:
56
+
57
+ - Upstream **write access** → push a branch in-repo and open the PR.
58
+ - **No write access** → fork, push to the fork, open a cross-repo PR.
59
+
60
+ Both take the **identical** review path.
61
+
62
+ ## Value gate and PR summary
63
+
64
+ Produce a one-sentence **value statement** as the PR summary:
65
+
66
+ > Resolves `<pain>`, by `<added/revised constraint>`, achieving `<quality/efficiency gain>`.
67
+
68
+ If no crisp value statement can be written, **open NO PR** — the batch is not worth promoting (this is the lightweight substitute for a metric/eval gate).
69
+
70
+ For review, keep each promoted `claim` paired with its `evidence` so a reviewer can check **claim ⇐ evidence**. A promoted change that later proves worse MUST be rolled back via `remove`/`replace` (git-traceable), not a manual file edit.
71
+
72
+ ## Output
73
+
74
+ Report in the interaction language: what was selected, the draft file(s) written under `templates/`, and the PR (branch or fork) with its value statement — or **"nothing promoted"** with the reason (value gate / nothing vetted / nothing general enough).
@@ -292,3 +292,13 @@ or a commit.
292
292
  - Commits remain outside verdict logic. If the surrounding workflow calls for
293
293
  a commit, create it only after the applicable checks are green; a commit must
294
294
  never alter, replace, or imply the review verdict.
295
+
296
+ ## Automatic Distillation
297
+
298
+ Read `workflow.auto_distill` from `.codexspec/config.yml` (**default `true`** — enabled unless explicitly set to the literal `false`; absent or any non-`false` value means enabled).
299
+
300
+ When `workflow.auto_distill` is enabled (not the literal `false`) AND this command reported success (§7.6), invoke `/codexspec:distill` exactly once on this session's interaction, then end.
301
+
302
+ - distill is non-blocking and non-interactive: it never prompts, never changes this command's verdict or report, and early-exits when there is nothing reusable to capture.
303
+ - distill only writes `candidate`/`vetted` records to `.codexspec/profile/`; it MUST NOT modify `requirements.md`, `spec.md`, `plan.md`, or `tasks.md`.
304
+ - Do not invoke distill when `auto_distill` is disabled or when this command did not report success.
@@ -622,3 +622,13 @@ When saving to a file, output the raw markdown content directly (without code bl
622
622
  - Include enough detail for reviewers to understand the changes
623
623
  - Do not include any AI attribution in the PR description
624
624
  - Focus on clarity and usefulness for code reviewers
625
+
626
+ ## Automatic Distillation
627
+
628
+ Read `workflow.auto_distill` from `.codexspec/config.yml` (**default `true`** — enabled unless explicitly set to the literal `false`; absent or any non-`false` value means enabled).
629
+
630
+ When `workflow.auto_distill` is enabled (not the literal `false`) AND a PR/MR description was generated successfully, invoke `/codexspec:distill` exactly once on this session's interaction, then end.
631
+
632
+ - distill is non-blocking and non-interactive: it never prompts and never alters the generated description; it early-exits when there is nothing reusable to capture.
633
+ - distill only writes records to `.codexspec/profile/`.
634
+ - Do not invoke distill when `auto_distill` is disabled or when no description was produced.
File without changes
File without changes
File without changes