auto-context-cli 1.0.5__tar.gz → 1.0.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 (37) hide show
  1. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/PKG-INFO +3 -3
  2. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/README.md +2 -2
  3. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/pyproject.toml +1 -1
  4. auto_context_cli-1.0.7/src/auto_context/__init__.py +16 -0
  5. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/init_cmd.py +145 -5
  6. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/mcp.py +7 -2
  7. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/memory.py +12 -1
  8. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/update_cmd.py +19 -3
  9. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/config.py +11 -12
  10. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/main.py +4 -2
  11. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/scanner.py +4 -3
  12. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/security.py +10 -5
  13. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/AGENTS.template.md +5 -0
  14. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/GEMINI.template.md +5 -0
  15. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/maintain_context.template.md +5 -0
  16. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/rules/code-standards.md +6 -0
  17. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/rules/hub-satellite-architecture.md +2 -2
  18. auto_context_cli-1.0.7/src/auto_context/templates/rules/registry.json +226 -0
  19. auto_context_cli-1.0.5/src/auto_context/__init__.py +0 -3
  20. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/.gitignore +0 -0
  21. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/LICENSE +0 -0
  22. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/__init__.py +0 -0
  23. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/import_cmd.py +0 -0
  24. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/scan_cmd.py +0 -0
  25. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/commands/share.py +0 -0
  26. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/context.local.template.md +0 -0
  27. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/context.template.md +0 -0
  28. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/mcp_config.template.json +0 -0
  29. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/profile.template.md +0 -0
  30. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/rule.template.md +0 -0
  31. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/rules/readme-docs-sync.md +0 -0
  32. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/rules/security-boundaries.md +0 -0
  33. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/skill.template.md +0 -0
  34. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/skills/browser-testing/SKILL.md +0 -0
  35. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/skills/docker-ops/SKILL.md +0 -0
  36. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/skills/git-workflow/SKILL.md +0 -0
  37. {auto_context_cli-1.0.5 → auto_context_cli-1.0.7}/src/auto_context/templates/skills/server-diagnostics/SKILL.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: auto-context-cli
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Autonomous context & instruction manager for AI coding assistants
5
5
  Author: Arghya Biswas
6
6
  License: MIT
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  [![PyPI - Version](https://img.shields.io/pypi/v/auto-context-cli.svg?color=blue)](https://pypi.org/project/auto-context-cli/)
22
22
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/auto-context-cli.svg)](https://pypi.org/project/auto-context-cli/)
23
- [![GitHub Release](https://img.shields.io/github/v/release/Cyber-arghya/auto-context?color=green)](https://github.com/Cyber-arghya/auto-context/releases/tag/v1.0.5)
23
+ [![GitHub Release](https://img.shields.io/github/v/release/Cyber-arghya/auto-context?color=green)](https://github.com/Cyber-arghya/auto-context/releases/tag/v1.0.7)
24
24
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
25
25
  [![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux%20WSL2-success.svg)](#-quick-one-line-install)
26
26
  [![AI Supported](https://img.shields.io/badge/AI%20Supported-Antigravity%20%7C%20Cursor%20%7C%20Claude%20Code%20%7C%20Copilot%20%7C%20Windsurf-orange.svg)](#-supported-ai-assistants)
@@ -83,7 +83,7 @@ Auto-Context supports both modern subcommands (`init-context <command>`) and sta
83
83
  | :--- | :--- | :--- |
84
84
  | `init-context init [DIR]` | `init-context [DIR]` | Scaffolds 5-tier context in active or specified directory. |
85
85
  | `init-context scan [DIR]` | `init-context --scan` | Stack-adaptive codebase scanner: generates satellites (.agents/context/). |
86
- | `init-context rule <name>` | `init-context -r <name>` | Scaffolds a new agent rule with YAML frontmatter. |
86
+ | `init-context rule <name>` | `init-context -r <name> [-a]` | Scaffolds a rule or appends to a domain with `registry.json` duplicate protection. |
87
87
  | `init-context skill <name>` | `init-context -s <name>` | Scaffolds a new agent skill directory with triggers & runbook. |
88
88
  | `init-context promote <name>` | — | Promotes local workspace rule/skill to Global Plugin & Central Memory. |
89
89
  | `init-context update` | — | Modular, non-breaking update for CLI, global plugin & system rules. |
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![PyPI - Version](https://img.shields.io/pypi/v/auto-context-cli.svg?color=blue)](https://pypi.org/project/auto-context-cli/)
8
8
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/auto-context-cli.svg)](https://pypi.org/project/auto-context-cli/)
9
- [![GitHub Release](https://img.shields.io/github/v/release/Cyber-arghya/auto-context?color=green)](https://github.com/Cyber-arghya/auto-context/releases/tag/v1.0.5)
9
+ [![GitHub Release](https://img.shields.io/github/v/release/Cyber-arghya/auto-context?color=green)](https://github.com/Cyber-arghya/auto-context/releases/tag/v1.0.7)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
11
11
  [![Platform](https://img.shields.io/badge/Platform-Windows%20%7C%20macOS%20%7C%20Linux%20WSL2-success.svg)](#-quick-one-line-install)
12
12
  [![AI Supported](https://img.shields.io/badge/AI%20Supported-Antigravity%20%7C%20Cursor%20%7C%20Claude%20Code%20%7C%20Copilot%20%7C%20Windsurf-orange.svg)](#-supported-ai-assistants)
@@ -69,7 +69,7 @@ Auto-Context supports both modern subcommands (`init-context <command>`) and sta
69
69
  | :--- | :--- | :--- |
70
70
  | `init-context init [DIR]` | `init-context [DIR]` | Scaffolds 5-tier context in active or specified directory. |
71
71
  | `init-context scan [DIR]` | `init-context --scan` | Stack-adaptive codebase scanner: generates satellites (.agents/context/). |
72
- | `init-context rule <name>` | `init-context -r <name>` | Scaffolds a new agent rule with YAML frontmatter. |
72
+ | `init-context rule <name>` | `init-context -r <name> [-a]` | Scaffolds a rule or appends to a domain with `registry.json` duplicate protection. |
73
73
  | `init-context skill <name>` | `init-context -s <name>` | Scaffolds a new agent skill directory with triggers & runbook. |
74
74
  | `init-context promote <name>` | — | Promotes local workspace rule/skill to Global Plugin & Central Memory. |
75
75
  | `init-context update` | — | Modular, non-breaking update for CLI, global plugin & system rules. |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "auto-context-cli"
3
- version = "1.0.5"
3
+ version = "1.0.7"
4
4
  description = "Autonomous context & instruction manager for AI coding assistants"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,16 @@
1
+ """auto-context: Autonomous context & instruction manager for AI coding assistants."""
2
+ import sys
3
+
4
+ # Ensure UTF-8 stream handling across all operating systems (guards Windows cp1252 charmap crashes)
5
+ if hasattr(sys.stdout, "reconfigure"):
6
+ try:
7
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
8
+ except Exception:
9
+ pass
10
+ if hasattr(sys.stderr, "reconfigure"):
11
+ try:
12
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
13
+ except Exception:
14
+ pass
15
+
16
+ __version__ = "1.0.6"
@@ -167,6 +167,10 @@ def init(
167
167
  for rule_file in (templates / "rules").glob("*.md"):
168
168
  _copy_if_missing(rule_file, rules_dest / rule_file.name, force=force, dry_run=dry_run)
169
169
 
170
+ reg_tmpl = templates / "rules" / "registry.json"
171
+ if reg_tmpl.exists():
172
+ _copy_if_missing(reg_tmpl, rules_dest / "registry.json", force=force, dry_run=dry_run)
173
+
170
174
  maintain_tmpl = templates / "maintain_context.template.md"
171
175
  if maintain_tmpl.exists():
172
176
  content = _render(maintain_tmpl, variables)
@@ -218,10 +222,10 @@ def init(
218
222
  ".env.*.local",
219
223
  ])
220
224
 
221
- # ── Stack-Adaptive Satellites (.agents/context/) ───────────────────────
225
+ # ── Stack-Adaptive Satellites & Executive Hub Sync ────────────────────
222
226
  try:
223
- from auto_context import scanner
224
- scanner.generate_satellites(project_dir, force=force)
227
+ from auto_context.commands import scan_cmd
228
+ scan_cmd.scan(target=project_dir, force=force)
225
229
  except Exception:
226
230
  pass
227
231
 
@@ -234,14 +238,116 @@ def init(
234
238
 
235
239
  # ── rule command ──────────────────────────────────────────────────────────────
236
240
 
241
+ def _load_registry(registry_file: Path) -> dict:
242
+ if registry_file.exists():
243
+ try:
244
+ import json
245
+ return json.loads(registry_file.read_text(encoding="utf-8"))
246
+ except Exception:
247
+ pass
248
+ tmpl = get_templates_dir() / "rules" / "registry.json"
249
+ if tmpl.exists():
250
+ try:
251
+ import json
252
+ return json.loads(tmpl.read_text(encoding="utf-8"))
253
+ except Exception:
254
+ pass
255
+ return {"version": "1.0.0", "domains": {}}
256
+
257
+
258
+ def _find_duplicate_rule(registry: dict, text_or_title: str) -> Optional[tuple[str, str, str]]:
259
+ import re
260
+ clean_query = re.sub(r"[^a-zA-Z0-9\s]", "", text_or_title).lower().strip()
261
+ query_words = set(w for w in clean_query.split() if len(w) > 3)
262
+
263
+ for d_key, domain in registry.get("domains", {}).items():
264
+ d_file = domain.get("file", f"{d_key}.md")
265
+ for r in domain.get("rules", []):
266
+ r_title = r.get("title", "")
267
+ r_id = r.get("id", "")
268
+ clean_title = re.sub(r"[^a-zA-Z0-9\s]", "", r_title).lower().strip()
269
+
270
+ if clean_query == clean_title or (len(clean_query) > 6 and clean_query in clean_title) or (len(clean_title) > 6 and clean_title in clean_query):
271
+ return d_key, r_title, d_file
272
+ if r_id and r_id in clean_query.replace(" ", "-"):
273
+ return d_key, r_title, d_file
274
+
275
+ r_keywords = set(k.lower() for k in r.get("keywords", []))
276
+ if query_words and len(query_words.intersection(r_keywords)) >= 2:
277
+ return d_key, r_title, d_file
278
+
279
+ return None
280
+
281
+
282
+ def _register_rule_in_registry(registry_file: Path, domain_key: str, rule_title: str, file_name: str) -> None:
283
+ import json
284
+ import re
285
+ from datetime import date
286
+
287
+ registry = _load_registry(registry_file)
288
+ if "domains" not in registry:
289
+ registry["domains"] = {}
290
+
291
+ if domain_key not in registry["domains"]:
292
+ registry["domains"][domain_key] = {
293
+ "file": file_name,
294
+ "name": domain_key.replace("-", " ").title(),
295
+ "description": f"Domain rules for {domain_key}",
296
+ "rules": []
297
+ }
298
+
299
+ clean_first_line = rule_title.strip().splitlines()[0].strip("* ").strip()
300
+ slug = re.sub(r"[^a-zA-Z0-9\s-]", "", clean_first_line).strip().lower().replace(" ", "-")
301
+ clean_words = [w for w in re.sub(r"[^a-zA-Z0-9\s]", "", clean_first_line).lower().split() if len(w) > 3]
302
+
303
+ existing_ids = [r.get("id") for r in registry["domains"][domain_key].get("rules", [])]
304
+ if slug not in existing_ids:
305
+ registry["domains"][domain_key]["rules"].append({
306
+ "id": slug,
307
+ "title": clean_first_line,
308
+ "keywords": clean_words
309
+ })
310
+ registry["last_updated"] = date.today().isoformat()
311
+ registry_file.parent.mkdir(parents=True, exist_ok=True)
312
+ registry_file.write_text(json.dumps(registry, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
313
+
314
+
315
+ def _append_guideline_to_file(dest_file: Path, text_to_append: str) -> None:
316
+ import re
317
+ content = dest_file.read_text(encoding="utf-8")
318
+ existing_nums = [int(m) for m in re.findall(r"(?m)^(\d+)\.\s+\*", content)]
319
+ next_num = (max(existing_nums) + 1) if existing_nums else 1
320
+
321
+ clean_guideline = text_to_append.strip()
322
+ if not re.match(r"^\d+\.\s+", clean_guideline):
323
+ if not clean_guideline.startswith("**"):
324
+ lines = clean_guideline.splitlines()
325
+ title = lines[0]
326
+ rest = lines[1:] if len(lines) > 1 else []
327
+ entry = f"{next_num}. **{title}**\n"
328
+ for r in rest:
329
+ entry += f" {r}\n"
330
+ else:
331
+ entry = f"{next_num}. {clean_guideline}\n"
332
+ else:
333
+ entry = f"{clean_guideline}\n"
334
+
335
+ if not content.endswith("\n"):
336
+ content += "\n"
337
+ content += entry
338
+ dest_file.write_text(content, encoding="utf-8")
339
+
340
+
237
341
  def new_rule(
238
342
  name: Annotated[str, typer.Argument(help="Rule name (e.g. code-standards).")],
239
343
  target: Annotated[Optional[Path], typer.Option("--target", "-t")] = None,
240
344
  global_: Annotated[bool, typer.Option("--global", "-g", help="Install into global plugin.")] = False,
241
345
  force: Annotated[bool, typer.Option("--force", "-f")] = False,
346
+ append: Annotated[bool, typer.Option("--append", "-a", help="Append guideline to existing rule domain.")] = False,
347
+ guideline: Annotated[Optional[str], typer.Option("--guideline", help="Guideline text to append to rule.")] = None,
242
348
  dry_run: Annotated[bool, typer.Option("--dry-run", "-d")] = False,
243
349
  ) -> None:
244
- """Scaffold a new agent rule file."""
350
+ """Scaffold a new agent rule file or append to an existing domain rule with duplicate guard."""
245
351
  clean_name = name.lower().replace(" ", "-").replace("_", "-").removesuffix(".md")
246
352
  title = clean_name.replace("-", " ").title()
247
353
 
@@ -251,9 +357,43 @@ def new_rule(
251
357
  else (target or Path.cwd()).resolve() / AGENTS_SUBDIR / "rules"
252
358
  )
253
359
  dest_file = dest_dir / f"{clean_name}.md"
360
+ registry_file = dest_dir / "registry.json"
254
361
 
255
362
  if dest_file.exists() and not force:
256
- console.print(f"[yellow][!] Rule already exists:[/] {dest_file}\n Use --force to overwrite.")
363
+ if append or guideline:
364
+ text_to_append = guideline or "New domain constraint and best practice."
365
+
366
+ # Check duplicate against registry
367
+ dup = _find_duplicate_rule(_load_registry(registry_file), text_to_append)
368
+ if dup:
369
+ dup_domain, dup_title, dup_file = dup
370
+ console.print(Panel(
371
+ f"[bold yellow]⚠️ Duplicate Rule Detected — Skipped![/]\n"
372
+ f"[white]The guideline '[cyan]{text_to_append}[/]' matches existing rule:\n"
373
+ f"[green]'{dup_title}'[/] inside [yellow]{dup_file}[/] (Domain: [cyan]{dup_domain}[/]).\n\n"
374
+ f"[dim]Registry checked: {registry_file}[/]",
375
+ border_style="yellow",
376
+ ))
377
+ return
378
+
379
+ if not dry_run:
380
+ _append_guideline_to_file(dest_file, text_to_append)
381
+ _register_rule_in_registry(registry_file, clean_name, text_to_append, dest_file.name)
382
+ if global_ and config.MEMORY_DIR.exists():
383
+ mem_dest = config.MEMORY_DIR / "rules" / f"{clean_name}.md"
384
+ if mem_dest.exists():
385
+ _append_guideline_to_file(mem_dest, text_to_append)
386
+ _register_rule_in_registry(config.MEMORY_DIR / "rules" / "registry.json", clean_name, text_to_append, dest_file.name)
387
+ console.print(Panel(
388
+ f"[bold green]✨ Rule Domain Updated & Registered![/]\n"
389
+ f"[yellow] Rule:[/] {clean_name}\n"
390
+ f"[cyan] File:[/] {dest_file}\n"
391
+ f"[cyan] Registry:[/] {registry_file}\n"
392
+ f"[green] Added:[/] {text_to_append}",
393
+ border_style="green",
394
+ ))
395
+ return
396
+ console.print(f"[yellow][!] Rule already exists:[/] {dest_file}\n Use --append (-a) with --guideline to add to this domain, or --force to overwrite.")
257
397
  raise typer.Exit(1)
258
398
 
259
399
  tmpl = get_templates_dir() / "rule.template.md"
@@ -10,8 +10,10 @@ from __future__ import annotations
10
10
 
11
11
  import json
12
12
  import os
13
- import stat
14
13
  from pathlib import Path
14
+ import shlex
15
+ import stat
16
+ import sys
15
17
  from typing import Annotated, Optional
16
18
 
17
19
  import typer
@@ -199,7 +201,10 @@ def add_mcp(
199
201
  elif command:
200
202
  server_cfg = {"command": command}
201
203
  if args:
202
- server_cfg["args"] = args.split()
204
+ try:
205
+ server_cfg["args"] = shlex.split(args, posix=(sys.platform != "win32"))
206
+ except Exception:
207
+ server_cfg["args"] = args.split()
203
208
  if env_var:
204
209
  server_cfg.setdefault("env", {})[env_var] = f"${{{env_var}}}"
205
210
  if not global_:
@@ -146,6 +146,14 @@ def _write_profile(repo_name: str) -> None:
146
146
 
147
147
  # ── local harvester ─────────────────────────────────────────────────────────
148
148
 
149
+ LOCAL_SYSTEM_RULES: frozenset[str] = frozenset({
150
+ "maintain_context.md",
151
+ "AGENTS.md",
152
+ "GEMINI.md",
153
+ "context.local.md",
154
+ })
155
+
156
+
149
157
  def _sync_local_to_memory(project_dir: Path) -> int:
150
158
  """Harvest newly created local project rules & skills into central memory."""
151
159
  harvested = 0
@@ -154,7 +162,10 @@ def _sync_local_to_memory(project_dir: Path) -> int:
154
162
  if local_rules.exists():
155
163
  (MEMORY_DIR / "rules").mkdir(parents=True, exist_ok=True)
156
164
  for rfile in local_rules.glob("*.md"):
157
- # Skip standard default boilerplates from polluting memory if already synced
165
+ # Exclude project-specific local instructions from polluting central memory
166
+ if rfile.name in LOCAL_SYSTEM_RULES or rfile.name.endswith(".local.md"):
167
+ continue
168
+
158
169
  dest = MEMORY_DIR / "rules" / rfile.name
159
170
  if not dest.exists():
160
171
  shutil.copy2(rfile, dest)
@@ -237,10 +237,20 @@ def update_global_plugin() -> int:
237
237
  core_files = [
238
238
  ("plugin.json", plugin_dir / "plugin.json"),
239
239
  ("plugin/rules/AGENTS.md", rules_dir / "AGENTS.md"),
240
+ ("plugin/rules/code-standards.md", rules_dir / "code-standards.md"),
241
+ ("plugin/rules/security-boundaries.md", rules_dir / "security-boundaries.md"),
242
+ ("plugin/rules/hub-satellite-architecture.md", rules_dir / "hub-satellite-architecture.md"),
243
+ ("plugin/rules/readme-docs-sync.md", rules_dir / "readme-docs-sync.md"),
244
+ ("plugin/rules/registry.json", rules_dir / "registry.json"),
240
245
  ("plugin/skills/auto-context/SKILL.md", skills_dir / "SKILL.md"),
246
+ ("plugin/skills/browser-testing/SKILL.md", plugin_dir / "skills" / "browser-testing" / "SKILL.md"),
247
+ ("plugin/skills/docker-ops/SKILL.md", plugin_dir / "skills" / "docker-ops" / "SKILL.md"),
248
+ ("plugin/skills/git-workflow/SKILL.md", plugin_dir / "skills" / "git-workflow" / "SKILL.md"),
249
+ ("plugin/skills/server-diagnostics/SKILL.md", plugin_dir / "skills" / "server-diagnostics" / "SKILL.md"),
241
250
  ]
242
251
 
243
252
  for rel_path, dest_path in core_files:
253
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
244
254
  url = f"{config.RAW_GITHUB_BASE}/{rel_path}"
245
255
  content = _download_text(url)
246
256
  if content:
@@ -252,10 +262,16 @@ def update_global_plugin() -> int:
252
262
  tmpl_dir = config.get_templates_dir()
253
263
  fallback_map = {
254
264
  "AGENTS.md": tmpl_dir / "AGENTS.template.md",
255
- "SKILL.md": tmpl_dir / "skill.template.md",
265
+ "SKILL.md": tmpl_dir / "skills" / dest_path.parent.name / "SKILL.md" if (tmpl_dir / "skills" / dest_path.parent.name / "SKILL.md").exists() else tmpl_dir / "skill.template.md",
266
+ "code-standards.md": tmpl_dir / "rules" / "code-standards.md",
267
+ "security-boundaries.md": tmpl_dir / "rules" / "security-boundaries.md",
268
+ "hub-satellite-architecture.md": tmpl_dir / "rules" / "hub-satellite-architecture.md",
269
+ "readme-docs-sync.md": tmpl_dir / "rules" / "readme-docs-sync.md",
270
+ "registry.json": tmpl_dir / "rules" / "registry.json",
256
271
  }
257
- if dest_path.name in fallback_map and fallback_map[dest_path.name].exists():
258
- shutil.copy2(fallback_map[dest_path.name], dest_path)
272
+ fb = fallback_map.get(dest_path.name)
273
+ if fb and fb.exists():
274
+ shutil.copy2(fb, dest_path)
259
275
  console.print(f" [green][✓] Restored from bundled template:[/] {dest_path.name}")
260
276
  updated_count += 1
261
277
 
@@ -10,22 +10,21 @@ from pathlib import Path
10
10
 
11
11
  def get_templates_dir() -> Path:
12
12
  """
13
- Return the templates directory in a way that works both during development
14
- (uv run / editable install) and after a wheel install (uv tool install .).
15
-
16
- importlib.resources.files() is the PEP 451-compliant way to locate data
17
- files that are bundled inside a Python package, regardless of whether the
18
- package is installed as a wheel, a zip, or an editable source tree.
13
+ Return the templates directory in a crash-proof, persistent way
14
+ that works during development, wheel installs, and virtual environments.
19
15
  """
16
+ pkg_tmpl = Path(__file__).parent / "templates"
17
+ if pkg_tmpl.is_dir():
18
+ return pkg_tmpl
19
+
20
20
  try:
21
- # Python 3.9+ path — works in wheel and editable installs
22
21
  ref = _pkg_resources.files("auto_context").joinpath("templates")
23
- # Materialise to a real Path so callers can use glob(), iterdir(), etc.
24
- with _pkg_resources.as_file(ref) as p:
25
- return p
22
+ if isinstance(ref, Path) and ref.is_dir():
23
+ return ref
26
24
  except Exception:
27
- # Hard fallback: source-tree layout (should never be reached in practice)
28
- return Path(__file__).parent / "templates"
25
+ pass
26
+
27
+ return pkg_tmpl
29
28
 
30
29
 
31
30
  # ── User home-relative roots ───────────────────────────────────────────────────
@@ -54,6 +54,8 @@ def root(
54
54
  env: Annotated[Optional[str], typer.Option("--env", help="Environment variables (KEY=val,KEY2=val2).")] = None,
55
55
  global_: Annotated[bool, typer.Option("--global", "-g", help="Target global plugin scope.")] = False,
56
56
  force: Annotated[bool, typer.Option("--force", "-f", help="Overwrite existing files.")] = False,
57
+ append: Annotated[bool, typer.Option("--append", "-a", help="Append to existing domain rule.")] = False,
58
+ guideline: Annotated[Optional[str], typer.Option("--guideline", help="Guideline text to append to rule.")] = None,
57
59
  dry_run: Annotated[bool, typer.Option("--dry-run", "-d", help="Preview without writing.")] = False,
58
60
  scan: Annotated[bool, typer.Option("--scan", help="Run stack-adaptive scanner on target directory.")] = False,
59
61
  version: bool = typer.Option(False, "--version", "-v", help="Show version and exit."),
@@ -69,7 +71,7 @@ def root(
69
71
  try:
70
72
  v = pkg_version("auto-context")
71
73
  except Exception:
72
- v = "1.0.5"
74
+ v = "1.0.7"
73
75
  console.print(f"init-context v{v}")
74
76
  raise typer.Exit()
75
77
 
@@ -78,7 +80,7 @@ def root(
78
80
 
79
81
  # ── Legacy flag routing ────────────────────────────────────────────────────
80
82
  if new_rule:
81
- init_cmd.new_rule(name=new_rule, target=target, global_=global_, force=force, dry_run=dry_run)
83
+ init_cmd.new_rule(name=new_rule, target=target, global_=global_, force=force, append=append, guideline=guideline, dry_run=dry_run)
82
84
  return
83
85
 
84
86
  if new_skill:
@@ -404,9 +404,9 @@ def generate_satellites(project_dir: Path, force: bool = False) -> dict[str, Pat
404
404
  write_idempotent(schema_path, schema_content, "Schema Satellite")
405
405
  generated["schema"] = schema_path
406
406
 
407
- # 3. gotchas.md (Critical Traps & Anti-Patterns — Replaces useless bug diaries)
407
+ # 3. gotchas.md (Critical Traps & Anti-Patterns — Preserved even with --force to prevent knowledge loss)
408
408
  gotchas_path = satellites_dir / "gotchas.md"
409
- if not gotchas_path.exists() or force:
409
+ if not gotchas_path.exists():
410
410
  gotchas_content = (
411
411
  "# Critical Gotchas, Anti-Patterns & Architecture Invariants\n\n"
412
412
  "> Institutional knowledge and non-obvious traps that cannot be guessed from reading code.\n\n"
@@ -414,8 +414,9 @@ def generate_satellites(project_dir: Path, force: bool = False) -> dict[str, Pat
414
414
  "1. **Port Invariants:** Inspect discovered ports before binding local services.\n"
415
415
  "2. **Secret Isolation:** Production secrets belong in `.env`; dummy dev accounts belong in `context.local.md`.\n"
416
416
  "3. **State Boundaries:** Verify migrations before initiating build or release cycles.\n"
417
+ "4. **Verified Executable Runbooks:** Never accept or generate slash-separated placeholder commands (`cmd1 / cmd2`). Runbooks must only contain 100% executable commands parsed from manifests or verified CLI tools.\n"
417
418
  )
418
419
  write_idempotent(gotchas_path, gotchas_content, "Gotchas Satellite")
419
- generated["gotchas"] = gotchas_path
420
+ generated["gotchas"] = gotchas_path
420
421
 
421
422
  return generated
@@ -33,20 +33,25 @@ PROHIBITED_EXTENSIONS: frozenset[str] = frozenset({
33
33
  ".pem", ".key", ".pkcs12", ".p12", ".pfx", ".kdbx",
34
34
  })
35
35
 
36
+ IGNORE_AUDIT_DIRS: frozenset[str] = frozenset({
37
+ ".git", "node_modules", ".venv", "venv", "__pycache__",
38
+ "dist", "build", ".next", ".turbo", ".nuxt", ".cache", ".idea", ".vscode"
39
+ })
40
+
36
41
  # ── Secret patterns (name → compiled regex) ──────────────────────────────────
37
42
  _RAW_PATTERNS: list[tuple[str, str]] = [
38
43
  ("GitHub Token", r"(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}"),
39
44
  ("GitHub Fine-grained PAT", r"github_pat_[A-Za-z0-9_]{50,}"),
40
45
  ("Stripe Live Key", r"sk_live_[0-9a-zA-Z]{24,}"),
41
- ("OpenAI API Key", r"sk-[a-zA-Z0-9_\-]{32,}"),
42
- ("Anthropic API Key", r"sk-ant-[a-zA-Z0-9_\-]{32,}"),
43
46
  ("OpenAI Project Key", r"sk-proj-[a-zA-Z0-9_\-]{40,}"),
47
+ ("Anthropic API Key", r"sk-ant-[a-zA-Z0-9_\-]{32,}"),
48
+ ("OpenAI API Key", r"sk-[a-zA-Z0-9_\-]{32,}"),
44
49
  ("Google API Key", r"AIza[0-9A-Za-z\-_]{35}"),
45
50
  ("AWS Access Key ID", r"AKIA[0-9A-Z]{16}"),
46
51
  ("Private Key Block", r"-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"),
47
52
  ("Hardcoded Secret/Password",
48
53
  r'(?i)(?:password|secret|api[_-]?key|access[_-]?token)\s*[:=]\s*[\'"]'
49
- r'(?!(?:YOUR_|REPLACE_|MOCK_|TEST_|DUMMY_|\{\{|<))'
54
+ r'(?!(?:YOUR_|REPLACE_|MOCK_|TEST_|DUMMY_|\{\{|\<|\$\{))'
50
55
  r'[A-Za-z0-9_\-\.\$\!\@\#\%\^\&\*\(\)\+]{8,}[\'"]'),
51
56
  ]
52
57
 
@@ -120,8 +125,8 @@ def audit_path(path: Path, *, single_file: bool = False) -> list[Violation]:
120
125
  violations.extend(_scan_file(path))
121
126
  else:
122
127
  for child in path.rglob("*"):
123
- # Skip .git internals
124
- if ".git" in child.parts:
128
+ # Skip VCS and bulky dependency directories
129
+ if any(part in IGNORE_AUDIT_DIRS for part in child.parts):
125
130
  continue
126
131
  if child.is_file():
127
132
  violations.extend(_scan_file(child))
@@ -45,3 +45,8 @@ Never blindly delete or truncate context:
45
45
  - `init-context -s <name> [-g]`: Create skill.
46
46
  - `init-context update`: Safely sync public rules and upstream capabilities (non-breaking).
47
47
  - `init-context memory push`: Push updates to central memory.
48
+
49
+ ### 7. First-Principles Gatekeeper: Idempotency, KISS, DRY & The Brutal Truth Filter
50
+ - **Strict Idempotency Proof ($f(f(x)) = f(x)$):** All code generation, filesystem operations, and context sync commands must be 100% idempotent. Running them once or 100 times must yield the exact same deterministic state without duplication or side-effects.
51
+ - **Modular, KISS & DRY:** Maintain clean separation of concerns. Prohibit copy-paste duplication, unnecessary abstractions, and monolithic bloat across all project code and context.
52
+ - **The Brutal Truth Filter:** Every idea, design proposal, or architectural suggestion must pass first-principles scrutiny. If an idea or approach fails "The Brutal Truth Filter" (adds needless complexity, breaks DRY/KISS, or solves the wrong problem), **immediately DROP the idea** and proactively notify the user with clear technical rationale instead of blindly building flawed requests.
@@ -45,3 +45,8 @@ Never blindly delete or truncate context:
45
45
  - `init-context -s <name> [-g]`: Create skill.
46
46
  - `init-context update`: Safely sync public rules and upstream capabilities (non-breaking).
47
47
  - `init-context memory push`: Push updates to central memory.
48
+
49
+ ### 7. First-Principles Gatekeeper: Idempotency, KISS, DRY & The Brutal Truth Filter
50
+ - **Strict Idempotency Proof ($f(f(x)) = f(x)$):** All code generation, filesystem operations, and context sync commands must be 100% idempotent. Running them once or 100 times must yield the exact same deterministic state without duplication or side-effects.
51
+ - **Modular, KISS & DRY:** Maintain clean separation of concerns. Prohibit copy-paste duplication, unnecessary abstractions, and monolithic bloat across all project code and context.
52
+ - **The Brutal Truth Filter:** Every idea, design proposal, or architectural suggestion must pass first-principles scrutiny. If an idea or approach fails "The Brutal Truth Filter" (adds needless complexity, breaks DRY/KISS, or solves the wrong problem), **immediately DROP the idea** and proactively notify the user with clear technical rationale instead of blindly building flawed requests.
@@ -46,3 +46,8 @@ Never blindly delete or truncate context:
46
46
  - `init-context scan`: Refresh stack-adaptive satellites (`routes.md`, `schema.md`, `gotchas.md`).
47
47
  - `init-context update`: Safely sync public rules and upstream capabilities (non-breaking).
48
48
  - `init-context memory push`: Push updates to central memory.
49
+
50
+ ### 7. First-Principles Gatekeeper: Idempotency, KISS, DRY & The Brutal Truth Filter
51
+ - **Strict Idempotency Proof ($f(f(x)) = f(x)$):** All code generation, filesystem operations, and context sync commands must be 100% idempotent. Running them once or 100 times must yield the exact same deterministic state without duplication or side-effects.
52
+ - **Modular, KISS & DRY:** Maintain clean separation of concerns. Prohibit copy-paste duplication, unnecessary abstractions, and monolithic bloat across all project code and context.
53
+ - **The Brutal Truth Filter:** Every idea, design proposal, or architectural suggestion must pass first-principles scrutiny. If an idea or approach fails "The Brutal Truth Filter" (adds needless complexity, breaks DRY/KISS, or solves the wrong problem), **immediately DROP the idea** and proactively notify the user with clear technical rationale instead of blindly building flawed requests.
@@ -22,3 +22,9 @@ Maintain readable, maintainable, modular, and type-safe codebases with clear err
22
22
  - Log actionable error messages that include relevant context (e.g. invalid inputs, resource identifiers).
23
23
  4. **Conventional Git Commits:**
24
24
  - Write semantic commit messages (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`).
25
+ 5. **Static Type Annotations & Null Safety:**
26
+ - Enforce explicit type annotations for public functions, parameters, and return types.
27
+ - Prohibit unchecked null / undefined dereferencing.
28
+ 6. **Named Constants Over Magic Values:**
29
+ - Replace literal strings and magic numbers with descriptive, uppercase module-level constants.
30
+ 7. **Immutable Data Structures**
@@ -23,8 +23,8 @@ Prevent monolithic context file bloat, eliminate token waste, ensure 100% idempo
23
23
  - User-written notes, secrets, and manual entries outside anchored zones must remain 100% untouched.
24
24
  - Check content hashes before disk writes to avoid unnecessary I/O.
25
25
 
26
- 3. **The Brutal Truth Filter (Zero Dead Logs):**
27
- - If an idea, log entry, or feature fails first-principles scrutiny, drop it immediately and inform the user.
26
+ 3. **The Brutal Truth Filter (Zero Dead Logs & Flawed Ideas):**
27
+ - If an idea, design approach, log entry, or feature proposal fails first-principles scrutiny, breaks KISS/DRY principles, or does not pass 'The Brutal Truth Filter', **immediately drop the idea and notify the user** with honest technical reasons.
28
28
  - Prohibit chronological bug cemeteries (`history.md` dumps of trivial solved bugs).
29
29
  - In their place, maintain **`gotchas.md`**: high-impact institutional knowledge, non-obvious architecture traps, port collisions, and edge-case invariants.
30
30
 
@@ -0,0 +1,226 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "last_updated": "2026-11-02",
4
+ "description": "Registry of unique rules and guidelines to prevent duplicates and enforce domain consolidation.",
5
+ "domains": {
6
+ "code-standards": {
7
+ "file": "code-standards.md",
8
+ "name": "Code Quality & Architecture Standards",
9
+ "description": "Clean code, defensive programming, typing, naming, and error handling",
10
+ "rules": [
11
+ {
12
+ "id": "defensive-coding-early-return",
13
+ "title": "Defensive Coding & Early Return",
14
+ "keywords": [
15
+ "guard clauses",
16
+ "early return",
17
+ "nested indentation",
18
+ "input validation",
19
+ "defensive coding"
20
+ ]
21
+ },
22
+ {
23
+ "id": "clean-code-srp",
24
+ "title": "Clean Code & Self-Documenting Logic",
25
+ "keywords": [
26
+ "single responsibility",
27
+ "srp",
28
+ "clean code",
29
+ "variable names",
30
+ "docstrings"
31
+ ]
32
+ },
33
+ {
34
+ "id": "structured-error-handling",
35
+ "title": "Structured Error Handling",
36
+ "keywords": [
37
+ "exception handling",
38
+ "empty catch",
39
+ "error logging",
40
+ "try catch"
41
+ ]
42
+ },
43
+ {
44
+ "id": "conventional-git-commits",
45
+ "title": "Conventional Git Commits",
46
+ "keywords": [
47
+ "commit messages",
48
+ "feat",
49
+ "fix",
50
+ "docs",
51
+ "chore",
52
+ "refactor"
53
+ ]
54
+ },
55
+ {
56
+ "id": "static-type-annotations",
57
+ "title": "Static Type Annotations & Null Safety",
58
+ "keywords": [
59
+ "type hints",
60
+ "type annotations",
61
+ "null safety",
62
+ "typing",
63
+ "null pointer"
64
+ ]
65
+ },
66
+ {
67
+ "id": "named-constants-over-magic-values",
68
+ "title": "Named Constants Over Magic Values",
69
+ "keywords": [
70
+ "magic numbers",
71
+ "literal strings",
72
+ "constants",
73
+ "magic values"
74
+ ]
75
+ },
76
+ {
77
+ "id": "immutable-data-structures",
78
+ "title": "Immutable Data Structures",
79
+ "keywords": [
80
+ "immutable",
81
+ "data",
82
+ "structures"
83
+ ]
84
+ }
85
+ ]
86
+ },
87
+ "security-boundaries": {
88
+ "file": "security-boundaries.md",
89
+ "name": "Security Boundaries & Secret Safety",
90
+ "description": "Secret protection, API keys, sandbox isolation, and network exposure",
91
+ "rules": [
92
+ {
93
+ "id": "zero-hardcoded-secrets",
94
+ "title": "Zero Hardcoded Secrets",
95
+ "keywords": [
96
+ "api keys",
97
+ "passwords",
98
+ "tokens",
99
+ "private keys",
100
+ "secrets",
101
+ ".env"
102
+ ]
103
+ },
104
+ {
105
+ "id": "local-sandbox-test-data-isolation",
106
+ "title": "Local Sandbox Test Data Isolation",
107
+ "keywords": [
108
+ "context.local.md",
109
+ "mock credentials",
110
+ "dummy logins",
111
+ "test accounts",
112
+ "git-ignored"
113
+ ]
114
+ },
115
+ {
116
+ "id": "public-exposure-boundaries",
117
+ "title": "Public Exposure Boundaries",
118
+ "keywords": [
119
+ "port binding",
120
+ "127.0.0.1",
121
+ "localhost",
122
+ "exposure",
123
+ "tunneling"
124
+ ]
125
+ }
126
+ ]
127
+ },
128
+ "hub-satellite-architecture": {
129
+ "file": "hub-satellite-architecture.md",
130
+ "name": "Hub & Satellite Modular Architecture & Idempotency Proof",
131
+ "description": "Context budget, satelittes, deterministic idempotency, and gotchas invariants",
132
+ "rules": [
133
+ {
134
+ "id": "hub-satellite-token-budget",
135
+ "title": "Hub & Satellite Pattern (Strict Token Budget)",
136
+ "keywords": [
137
+ "context.md",
138
+ "satellites",
139
+ "routes.md",
140
+ "schema.md",
141
+ "gotchas.md",
142
+ "token budget"
143
+ ]
144
+ },
145
+ {
146
+ "id": "strict-idempotency-proof",
147
+ "title": "Strict Idempotency Proof",
148
+ "keywords": [
149
+ "idempotency",
150
+ "anchored blocks",
151
+ "re-running",
152
+ "deterministic",
153
+ "hash check"
154
+ ]
155
+ },
156
+ {
157
+ "id": "brutal-truth-filter",
158
+ "title": "The Brutal Truth Filter (Zero Dead Logs)",
159
+ "keywords": [
160
+ "gotchas.md",
161
+ "anti-patterns",
162
+ "dead logs",
163
+ "bug cemeteries",
164
+ "invariants"
165
+ ]
166
+ },
167
+ {
168
+ "id": "kiss-dry-heuristics",
169
+ "title": "KISS & DRY Heuristics",
170
+ "keywords": [
171
+ "convention heuristics",
172
+ "graceful degradation",
173
+ "lightweight scanners"
174
+ ]
175
+ }
176
+ ]
177
+ },
178
+ "readme-docs-sync": {
179
+ "file": "readme-docs-sync.md",
180
+ "name": "Mandatory README & Documentation Synchronization",
181
+ "description": "Documentation synchronization for CLI commands, options, and workflows",
182
+ "rules": [
183
+ {
184
+ "id": "zero-undocumented-features",
185
+ "title": "Zero Undocumented Features",
186
+ "keywords": [
187
+ "readme",
188
+ "documentation",
189
+ "docs sync",
190
+ "bengali guide",
191
+ "options table"
192
+ ]
193
+ },
194
+ {
195
+ "id": "clear-executable-instructions",
196
+ "title": "Clear & Executable Instructions",
197
+ "keywords": [
198
+ "executable commands",
199
+ "cli examples",
200
+ "options tables",
201
+ "syntax"
202
+ ]
203
+ },
204
+ {
205
+ "id": "safety-non-breaking-boundaries",
206
+ "title": "Safety & Non-Breaking Boundaries",
207
+ "keywords": [
208
+ "non-breaking",
209
+ "protection guarantees",
210
+ "custom rules preserve"
211
+ ]
212
+ },
213
+ {
214
+ "id": "release-version-alignment",
215
+ "title": "Release Version Alignment",
216
+ "keywords": [
217
+ "version bump",
218
+ "pyproject.toml",
219
+ "tags",
220
+ "release badges"
221
+ ]
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ }
@@ -1,3 +0,0 @@
1
- """auto-context: Autonomous context & instruction manager for AI coding assistants."""
2
-
3
- __version__ = "1.0.5"