yjcli 0.0.2__tar.gz → 0.0.3__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 (88) hide show
  1. {yjcli-0.0.2 → yjcli-0.0.3}/PKG-INFO +10 -3
  2. {yjcli-0.0.2 → yjcli-0.0.3}/README.md +9 -2
  3. {yjcli-0.0.2 → yjcli-0.0.3}/pyproject.toml +1 -1
  4. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/__init__.py +0 -2
  5. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/cli.py +16 -2
  6. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/commands/add.py +4 -1
  7. yjcli-0.0.3/src/yjcli/commands/sync.py +75 -0
  8. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/AGENTS.md +14 -1
  9. yjcli-0.0.3/src/yjcli/data/templates/TOOLS.md +168 -0
  10. yjcli-0.0.3/src/yjcli/services/sync.py +68 -0
  11. yjcli-0.0.3/src/yjcli/services/wiring.py +42 -0
  12. yjcli-0.0.2/src/yjcli/services/wiring.py +0 -61
  13. {yjcli-0.0.2 → yjcli-0.0.3}/.gitignore +0 -0
  14. {yjcli-0.0.2 → yjcli-0.0.3}/LICENSE +0 -0
  15. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/__main__.py +0 -0
  16. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/commands/__init__.py +0 -0
  17. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/commands/init_cmd.py +0 -0
  18. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/__init__.py +0 -0
  19. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/output-discipline.md +0 -0
  20. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-backend-msa.md +0 -0
  21. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-backend-service.md +0 -0
  22. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-browser-extension.md +0 -0
  23. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-cli.md +0 -0
  24. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-frontend.md +0 -0
  25. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-mobile-app.md +0 -0
  26. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/skill-pc-app.md +0 -0
  27. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/claude-rules/yj-arch-core.md +0 -0
  28. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/output-discipline.mdc +0 -0
  29. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-backend-msa.mdc +0 -0
  30. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-backend-service.mdc +0 -0
  31. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-browser-extension.mdc +0 -0
  32. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-cli.mdc +0 -0
  33. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-frontend.mdc +0 -0
  34. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-mobile-app.mdc +0 -0
  35. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/skill-pc-app.mdc +0 -0
  36. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/cursor-rules/yj-arch-core.mdc +0 -0
  37. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-arch-core/SKILL.md +0 -0
  38. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-backend-msa/SKILL.md +0 -0
  39. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-backend-service/SKILL.md +0 -0
  40. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-browser-extension/SKILL.md +0 -0
  41. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-cli/SKILL.md +0 -0
  42. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-frontend/SKILL.md +0 -0
  43. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-mobile-app/SKILL.md +0 -0
  44. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/skills/yj-pc-app/SKILL.md +0 -0
  45. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/.gitignore +0 -0
  46. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/Makefile +0 -0
  47. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/make.bat +0 -0
  48. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend/.env.development +0 -0
  49. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend/.env.examples +0 -0
  50. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend/.env.local-dev +0 -0
  51. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend/.env.production +0 -0
  52. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend-service/.env.development +0 -0
  53. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend-service/.env.examples +0 -0
  54. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend-service/.env.local-dev +0 -0
  55. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/backend-service/.env.production +0 -0
  56. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/browser-extension/.env.development +0 -0
  57. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/browser-extension/.env.examples +0 -0
  58. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/browser-extension/.env.local-dev +0 -0
  59. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/browser-extension/.env.production +0 -0
  60. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/cli/.env.development +0 -0
  61. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/cli/.env.examples +0 -0
  62. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/cli/.env.local-dev +0 -0
  63. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/cli/.env.production +0 -0
  64. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/.env.development +0 -0
  65. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/.env.examples +0 -0
  66. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/.env.local-dev +0 -0
  67. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/.env.production +0 -0
  68. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/package.json +0 -0
  69. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/frontend/vite.config.ts +0 -0
  70. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/mobile-app/.env.development +0 -0
  71. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/mobile-app/.env.examples +0 -0
  72. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/mobile-app/.env.local-dev +0 -0
  73. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/mobile-app/.env.production +0 -0
  74. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/pc-app/.env.development +0 -0
  75. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/pc-app/.env.examples +0 -0
  76. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/pc-app/.env.local-dev +0 -0
  77. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/pc-app/.env.production +0 -0
  78. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/scripts/run.bat +0 -0
  79. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/platform/scripts/run.sh +0 -0
  80. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/data/templates/settings.json +0 -0
  81. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/modules/__init__.py +0 -0
  82. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/modules/constants.py +0 -0
  83. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/modules/fsutil.py +0 -0
  84. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/modules/paths.py +0 -0
  85. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/modules/prompt.py +0 -0
  86. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/services/__init__.py +0 -0
  87. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/services/scaffold.py +0 -0
  88. {yjcli-0.0.2 → yjcli-0.0.3}/src/yjcli/services/status.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yjcli
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Scaffold platforms, services, and AI agent wiring (Cursor/Claude) for YJ architecture repos.
5
5
  Project-URL: Homepage, https://github.com/yseiren87/yjcli
6
6
  Project-URL: Repository, https://github.com/yseiren87/yjcli
@@ -37,13 +37,13 @@ uv run yjcli --help
37
37
  uv run yjcli --version
38
38
  ```
39
39
 
40
- Version: `pyproject.toml` / `src/yjcli/__init__.py`.
40
+ Version: `pyproject.toml` `[project].version` only (`yjcli --version` reads package metadata).
41
41
 
42
42
  ## Commands
43
43
 
44
44
  ```bash
45
45
  # Bootstrap: AGENTS.md/CLAUDE.md, .cursor/.claude skills+rules,
46
- # root Makefile/.gitignore, selected platform roots.
46
+ # root Makefile/TOOLS.md/.gitignore, selected platform roots.
47
47
  uv run yjcli init
48
48
  uv run yjcli init --all
49
49
  uv run yjcli init -p backend -p frontend
@@ -56,5 +56,12 @@ uv run yjcli add platform -p cli
56
56
  uv run yjcli add service # interactive
57
57
  uv run yjcli add service -p backend -n api
58
58
 
59
+ uv run yjcli sync agents # AGENTS.md → CLAUDE.md
60
+ uv run yjcli sync skills # packaged skills → .cursor/.claude
61
+ uv run yjcli sync rules # packaged rules → .cursor/.claude
62
+ uv run yjcli sync all # agents + skills + rules
59
63
  uv run yjcli doctor # packaged asset sanity check
60
64
  ```
65
+
66
+ Edit `AGENTS.md` only; run `yjcli sync agents` (or `sync all`) to refresh `CLAUDE.md`.
67
+
@@ -12,13 +12,13 @@ uv run yjcli --help
12
12
  uv run yjcli --version
13
13
  ```
14
14
 
15
- Version: `pyproject.toml` / `src/yjcli/__init__.py`.
15
+ Version: `pyproject.toml` `[project].version` only (`yjcli --version` reads package metadata).
16
16
 
17
17
  ## Commands
18
18
 
19
19
  ```bash
20
20
  # Bootstrap: AGENTS.md/CLAUDE.md, .cursor/.claude skills+rules,
21
- # root Makefile/.gitignore, selected platform roots.
21
+ # root Makefile/TOOLS.md/.gitignore, selected platform roots.
22
22
  uv run yjcli init
23
23
  uv run yjcli init --all
24
24
  uv run yjcli init -p backend -p frontend
@@ -31,5 +31,12 @@ uv run yjcli add platform -p cli
31
31
  uv run yjcli add service # interactive
32
32
  uv run yjcli add service -p backend -n api
33
33
 
34
+ uv run yjcli sync agents # AGENTS.md → CLAUDE.md
35
+ uv run yjcli sync skills # packaged skills → .cursor/.claude
36
+ uv run yjcli sync rules # packaged rules → .cursor/.claude
37
+ uv run yjcli sync all # agents + skills + rules
34
38
  uv run yjcli doctor # packaged asset sanity check
35
39
  ```
40
+
41
+ Edit `AGENTS.md` only; run `yjcli sync agents` (or `sync all`) to refresh `CLAUDE.md`.
42
+
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yjcli"
3
- version = "0.0.2"
3
+ version = "0.0.3"
4
4
  description = "Scaffold platforms, services, and AI agent wiring (Cursor/Claude) for YJ architecture repos."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,3 +1 @@
1
1
  """yjcli — platform/service scaffold and agent wiring."""
2
-
3
- __version__ = "0.0.1"
@@ -2,25 +2,38 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from importlib.metadata import PackageNotFoundError, version
5
6
  from typing import Optional
6
7
 
7
8
  import typer
8
9
 
9
- from yjcli import __version__
10
10
  from yjcli.commands import add
11
+ from yjcli.commands import sync
11
12
  from yjcli.commands.init_cmd import init_cmd
12
13
  from yjcli.services import status
13
14
 
15
+
16
+ def package_version() -> str:
17
+ """Installed package version from pyproject metadata (single source)."""
18
+ try:
19
+ return version("yjcli")
20
+ except PackageNotFoundError:
21
+ return "0.0.0+local"
22
+
23
+
24
+ _HELP_CONTEXT = {"help_option_names": ["-h", "--help"]}
25
+
14
26
  app = typer.Typer(
15
27
  name="yjcli",
16
28
  help="Scaffold YJ platforms/services and Cursor/Claude agent wiring.",
17
29
  no_args_is_help=True,
30
+ context_settings=_HELP_CONTEXT,
18
31
  )
19
32
 
20
33
 
21
34
  def _version_callback(value: bool) -> None:
22
35
  if value:
23
- typer.echo(__version__)
36
+ typer.echo(package_version())
24
37
  raise typer.Exit()
25
38
 
26
39
 
@@ -40,6 +53,7 @@ def main(
40
53
 
41
54
  app.command("init")(init_cmd)
42
55
  app.add_typer(add.app, name="add")
56
+ app.add_typer(sync.app, name="sync")
43
57
 
44
58
 
45
59
  @app.command("doctor")
@@ -10,7 +10,10 @@ import typer
10
10
  from yjcli.modules.constants import PLATFORMS
11
11
  from yjcli.services import scaffold
12
12
 
13
- app = typer.Typer(help="Add a platform or service to the current repo.")
13
+ app = typer.Typer(
14
+ help="Add a platform or service to the current repo.",
15
+ context_settings={"help_option_names": ["-h", "--help"]},
16
+ )
14
17
 
15
18
 
16
19
  @app.command("platform")
@@ -0,0 +1,75 @@
1
+ """yjcli sync <what> — refresh agent docs / skills / rules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ from yjcli.services import sync as sync_svc
10
+
11
+ app = typer.Typer(
12
+ help="Sync AGENTS.md mirror, skills, or rules into the target repo.",
13
+ no_args_is_help=True,
14
+ context_settings={"help_option_names": ["-h", "--help"]},
15
+ )
16
+
17
+
18
+ @app.command("agents")
19
+ def sync_agents(
20
+ path: Path = typer.Option(
21
+ Path("."),
22
+ "--path",
23
+ help="Target repository root (default: cwd).",
24
+ file_okay=False,
25
+ dir_okay=True,
26
+ resolve_path=True,
27
+ ),
28
+ ) -> None:
29
+ """Overwrite CLAUDE.md from AGENTS.md (edit AGENTS.md only)."""
30
+ sync_svc.sync_agents(path)
31
+
32
+
33
+ @app.command("skills")
34
+ def sync_skills(
35
+ path: Path = typer.Option(
36
+ Path("."),
37
+ "--path",
38
+ help="Target repository root (default: cwd).",
39
+ file_okay=False,
40
+ dir_okay=True,
41
+ resolve_path=True,
42
+ ),
43
+ ) -> None:
44
+ """Overwrite .cursor/.claude skills from the yjcli package."""
45
+ sync_svc.sync_skills(path, force=True)
46
+
47
+
48
+ @app.command("rules")
49
+ def sync_rules(
50
+ path: Path = typer.Option(
51
+ Path("."),
52
+ "--path",
53
+ help="Target repository root (default: cwd).",
54
+ file_okay=False,
55
+ dir_okay=True,
56
+ resolve_path=True,
57
+ ),
58
+ ) -> None:
59
+ """Overwrite .cursor/.claude rules from the yjcli package."""
60
+ sync_svc.sync_rules(path, force=True)
61
+
62
+
63
+ @app.command("all")
64
+ def sync_all(
65
+ path: Path = typer.Option(
66
+ Path("."),
67
+ "--path",
68
+ help="Target repository root (default: cwd).",
69
+ file_okay=False,
70
+ dir_okay=True,
71
+ resolve_path=True,
72
+ ),
73
+ ) -> None:
74
+ """Sync AGENTS.md → CLAUDE.md, skills, and rules together."""
75
+ sync_svc.sync_all(path, force=True)
@@ -10,9 +10,21 @@
10
10
 
11
11
  ## 2. Repository layout
12
12
 
13
+ Paths from `yjcli init` are fixed; add platform/app rows with `<path>` / `<role>` as needed.
14
+
13
15
  | Path | Role |
14
16
  |------|------|
15
- | `<path>/` | `<role>` |
17
+ | `AGENTS.md` | Agent / contributor guide (this file) |
18
+ | `CLAUDE.md` | Mirror of `AGENTS.md` — do not edit; refresh with `yjcli sync agents` |
19
+ | `TOOLS.md` | Release-tools feature spec (version / deploy / git) |
20
+ | `Makefile` | Root run entry (`make run PLATFORM=… NAME=…`) |
21
+ | `make.bat` | Windows counterpart of root Makefile |
22
+ | `.gitignore` | Canonical ignore rules |
23
+ | `.cursor/skills/` | Cursor skills (copied from package) |
24
+ | `.cursor/rules/` | Cursor rules (copied from package) |
25
+ | `.claude/skills/` | Claude skills (copied from package) |
26
+ | `.claude/rules/` | Claude rules (copied from package) |
27
+ | `.claude/settings.json` | Claude settings |
16
28
  | `<path>/` | `<role>` |
17
29
  | `<path>/` | `<role>` |
18
30
 
@@ -105,5 +117,6 @@
105
117
 
106
118
  | Kind | Location |
107
119
  |------|----------|
120
+ | Release tools spec | `TOOLS.md` |
108
121
  | `<kind>` | `<path>` |
109
122
  | `<kind>` | `<path>` |
@@ -0,0 +1,168 @@
1
+ # TOOLS.md — Release tools (feature spec)
2
+
3
+ Language-agnostic **feature specification** for local release tooling.
4
+ Not an architecture diary and not tied to a specific stack.
5
+
6
+ **How to use**
7
+
8
+ 1. Keep or copy this file into a repo.
9
+ 2. Fill project-specific facts in §0 (inputs).
10
+ 3. Ask an agent/dev: *Implement TOOLS.md using \<language/toolchain\>.*
11
+ 4. Implementation may be Make + scripts, a small CLI, CI jobs, etc. — as long as behaviors below are met.
12
+
13
+ ---
14
+
15
+ ## 0. Project inputs (fill before implement)
16
+
17
+ | ID | Input | Value |
18
+ |----|--------|--------|
19
+ | I1 | Product / package name | `<name>` |
20
+ | I2 | Canonical version location | `<file + field>` — semver `X.Y.Z` |
21
+ | I3 | Other version mirrors (optional) | `<paths or none>` |
22
+ | I4 | Git root | `<path>` |
23
+ | I5 | Default branch | `<branch>` |
24
+ | I6 | Git remote name + URL | `<origin + url>` |
25
+ | I7 | Production publish target | `<registry/host + package id>` |
26
+ | I8 | Staging publish target (optional) | `<or none>` |
27
+ | I9 | Secrets store | `<e.g. ignored .env path>` |
28
+ | I10 | Secret: publish token name(s) | `<ENV keys>` |
29
+ | I11 | Secret: git push token name (if HTTPS PAT) | `<ENV key or none if SSH>` |
30
+ | I12 | Version tag pattern | `<e.g. vX.Y.Z or release/X.Y.Z>` |
31
+ | I13 | Preferred invoke style | `<make / cli / npm scripts / …>` |
32
+
33
+ ---
34
+
35
+ ## 1. Goals
36
+
37
+ Provide three capabilities, invocable from a single local entry surface (I13):
38
+
39
+ | ID | Capability | Intent |
40
+ |----|------------|--------|
41
+ | C1 | **Version** | Read/bump canonical semver; keep mirrors in sync if any |
42
+ | C2 | **Deploy** | Build and publish artifacts to configured targets |
43
+ | C3 | **Git release** | Commit (optional), tag, push using non-interactive auth when scripted |
44
+
45
+ Out of scope: app business logic, scaffold of product code, editor agent rules (see `AGENTS.md`).
46
+
47
+ ---
48
+
49
+ ## 2. Shared requirements
50
+
51
+ | ID | Requirement |
52
+ |----|-------------|
53
+ | S1 | No secrets in git. Tokens only via I9/I10/I11. |
54
+ | S2 | Destructive steps (publish, push, tag) require explicit confirmation, default **No**. |
55
+ | S3 | User can abort the whole flow with a clear cancel word (recommend `exit`) on any prompt. |
56
+ | S4 | Steps are logged with clear section boundaries (readable in a terminal). |
57
+ | S5 | Missing config/secrets → fail with actionable message (what to set, where). |
58
+ | S6 | Do not force-overwrite existing git tags matching I12 unless the project explicitly opts in (default: **fail**). |
59
+
60
+ ---
61
+
62
+ ## 3. C1 — Version management
63
+
64
+ ### 3.1 Behaviors
65
+
66
+ | ID | Behavior |
67
+ |----|----------|
68
+ | V1 | Read version from I2; must match `X.Y.Z`. |
69
+ | V2 | Support bumps: `major` → `X+1.0.0`, `minor` → `X.Y+1.0`, `fix` → `X.Y.Z+1`. |
70
+ | V3 | After bump, write I2; if I3 is non-empty, update those to the same version. |
71
+ | V4 | Optional: show latest version from production target I7 when query is possible; if unavailable, warn and continue. |
72
+ | V5 | Interactive bump: ask whether to bump; if yes, ask `major`/`minor`/`fix` (invalid/empty → re-ask; cancel per S3). |
73
+ | V6 | Non-interactive mode (optional): accept bump kind via flags/env; do not hang on stdin. |
74
+
75
+ ### 3.2 Acceptance
76
+
77
+ - Given I2 = `1.2.3`, bump `fix` → I2 (and I3) become `1.2.4`.
78
+ - Invalid version string in I2 → hard fail before publish/tag.
79
+
80
+ ---
81
+
82
+ ## 4. C2 — Deploy / publish
83
+
84
+ ### 4.1 Behaviors
85
+
86
+ | ID | Behavior |
87
+ |----|----------|
88
+ | D1 | **Test**: run project-defined verification before publish (unit/smoke/e2e — project chooses). Fail stops the pipeline. |
89
+ | D2 | **Build**: produce publishable artifact(s) for I7 (and I8 if used). |
90
+ | D3 | **Validate** (recommended): dry-run or registry check when the ecosystem supports it. |
91
+ | D4 | **Confirm** then **Publish** to I7 using I10. Default confirm = No. |
92
+ | D5 | Provide a **staging path** when I8 exists: publish to I8 without requiring a version bump or git push (unless project opts in). |
93
+ | D6 | Production path may optionally chain into C3 after successful publish (project chooses). |
94
+
95
+ ### 4.2 Acceptance
96
+
97
+ - Failed D1 or D2 → no publish.
98
+ - Declined confirm → no publish, exit cleanly.
99
+ - Missing I10 → fail per S5.
100
+
101
+ ---
102
+
103
+ ## 5. C3 — Git release (push / tag)
104
+
105
+ ### 5.1 Behaviors
106
+
107
+ | ID | Behavior |
108
+ |----|----------|
109
+ | G1 | Operate only on git root I4. |
110
+ | G2 | Show status: branch, remote I6, current version (I2), whether tag I12(version) exists. |
111
+ | G3 | **Commit** (optional): if working tree dirty, ask to commit; require non-empty message; cancel per S3. If clean, skip. |
112
+ | G4 | **Tag**: create tag from I12 + current I2 version; if exists → fail (S6). |
113
+ | G5 | **Push**: push current branch + new tag to I6. |
114
+ | G6 | Auth: non-interactive for scripted use (PAT from I11 for HTTPS, or SSH agent). Do not open browser login flows in the happy path. |
115
+ | G7 | Never persist raw tokens into remote URLs in git config. |
116
+
117
+ ### 5.2 Acceptance
118
+
119
+ - Clean tree + new version tag → tag created and pushed when confirmed.
120
+ - Existing tag → fail, no force-push by default.
121
+ - Missing I11 when HTTPS PAT is required → fail per S5.
122
+
123
+ ---
124
+
125
+ ## 6. Suggested entry surface (names are free)
126
+
127
+ Implementers pick I13 names; map behaviors roughly like:
128
+
129
+ | Entry | Calls |
130
+ |-------|--------|
131
+ | Help | List entries |
132
+ | Version / bump | C1 |
133
+ | Deploy production | C1 (optional ask) → C2 → optional C3 |
134
+ | Deploy staging | C2 against I8 (usually skip C1 bump & C3) |
135
+ | Git release only | C3 |
136
+
137
+ Exact command names are **not** part of this spec.
138
+
139
+ ---
140
+
141
+ ## 7. Non-goals
142
+
143
+ - Mandating Python/Node/Go/Make/etc.
144
+ - Mandating a `tools/` directory layout.
145
+ - Replacing product CI; this spec may be implemented as CI, local scripts, or both.
146
+ - Defining app platforms, scaffolds, or agent skills.
147
+
148
+ ---
149
+
150
+ ## 8. Implementation notes for agents
151
+
152
+ When asked to implement this file:
153
+
154
+ 1. Read §0 values; if empty, ask the user for I1–I13 before coding.
155
+ 2. Choose toolchain from the user’s language request + existing repo norms.
156
+ 3. Implement C1→C2→C3 against the IDs above; keep secrets out of the repo.
157
+ 4. Add a short “Implemented by” note under §9 (paths to scripts/workflows only).
158
+
159
+ ---
160
+
161
+ ## 9. Implemented by (fill after coding)
162
+
163
+ | Capability | Entry / path |
164
+ |------------|----------------|
165
+ | C1 Version | `<…>` |
166
+ | C2 Deploy | `<…>` |
167
+ | C3 Git release | `<…>` |
168
+ | Secrets example | `<…>` |
@@ -0,0 +1,68 @@
1
+ """Sync agent docs / skills / rules into a target repo."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ from yjcli.modules import paths
10
+ from yjcli.modules.fsutil import copy_file, copy_tree, ensure_real_dir
11
+ from yjcli.modules.prompt import abort
12
+
13
+
14
+ def sync_agents(root: Path) -> None:
15
+ """Overwrite CLAUDE.md from AGENTS.md (AGENTS.md is the source of truth)."""
16
+ agents = root / "AGENTS.md"
17
+ claude = root / "CLAUDE.md"
18
+ if not agents.is_file():
19
+ abort(f"missing {agents} (edit AGENTS.md first)")
20
+ copy_file(agents, claude, force=True)
21
+ typer.echo("synced: AGENTS.md -> CLAUDE.md")
22
+
23
+
24
+ def sync_skills(root: Path, *, force: bool = True) -> None:
25
+ """Copy packaged skills into .cursor/skills and .claude/skills."""
26
+ cursor_skills = root / ".cursor" / "skills"
27
+ claude_skills = root / ".claude" / "skills"
28
+ ensure_real_dir(cursor_skills)
29
+ ensure_real_dir(claude_skills)
30
+
31
+ skills_root = paths.skills_dir()
32
+ if not skills_root.is_dir():
33
+ abort(f"missing packaged skills: {skills_root}")
34
+
35
+ for skill_dir in sorted(p for p in skills_root.iterdir() if p.is_dir()):
36
+ name = skill_dir.name
37
+ copy_tree(skill_dir, cursor_skills / name, force=force)
38
+ copy_tree(skill_dir, claude_skills / name, force=force)
39
+ typer.echo("synced: packaged skills -> .cursor/skills, .claude/skills")
40
+
41
+
42
+ def sync_rules(root: Path, *, force: bool = True) -> None:
43
+ """Copy packaged rules into .cursor/rules and .claude/rules."""
44
+ cursor_rules = root / ".cursor" / "rules"
45
+ claude_rules = root / ".claude" / "rules"
46
+ ensure_real_dir(cursor_rules)
47
+ ensure_real_dir(claude_rules)
48
+
49
+ cursor_src = paths.cursor_rules_dir()
50
+ claude_src = paths.claude_rules_dir()
51
+ if not cursor_src.is_dir() and not claude_src.is_dir():
52
+ abort("missing packaged cursor/claude rules")
53
+
54
+ for rule_file in sorted(cursor_src.glob("*")):
55
+ if rule_file.is_file():
56
+ copy_file(rule_file, cursor_rules / rule_file.name, force=force)
57
+
58
+ for rule_file in sorted(claude_src.glob("*")):
59
+ if rule_file.is_file():
60
+ copy_file(rule_file, claude_rules / rule_file.name, force=force)
61
+ typer.echo("synced: packaged rules -> .cursor/rules, .claude/rules")
62
+
63
+
64
+ def sync_all(root: Path, *, force: bool = True) -> None:
65
+ """Sync AGENTS.md mirror, skills, and rules in one shot."""
66
+ sync_agents(root)
67
+ sync_skills(root, force=force)
68
+ sync_rules(root, force=force)
@@ -0,0 +1,42 @@
1
+ """Copy packaged Cursor/Claude skills & rules into a target repo."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ from yjcli.modules import paths
10
+ from yjcli.modules.fsutil import copy_file
11
+ from yjcli.services import sync as sync_svc
12
+
13
+
14
+ def ensure_agent_wiring(root: Path, *, force: bool = False) -> None:
15
+ """Install AGENTS docs, skills, rules, and Claude settings; mirror CLAUDE.md."""
16
+ templates = paths.templates_dir()
17
+ agents_src = templates / "AGENTS.md"
18
+ settings_src = templates / "settings.json"
19
+ if not agents_src.is_file():
20
+ raise FileNotFoundError(f"missing template: {agents_src}")
21
+ if not settings_src.is_file():
22
+ raise FileNotFoundError(f"missing template: {settings_src}")
23
+
24
+ copy_file(agents_src, root / "AGENTS.md", force=force)
25
+ # CLAUDE.md is always a mechanical mirror of AGENTS.md (never edited by hand).
26
+ if (root / "AGENTS.md").is_file():
27
+ sync_svc.sync_agents(root)
28
+
29
+ sync_svc.sync_skills(root, force=force)
30
+ sync_svc.sync_rules(root, force=force)
31
+ copy_file(settings_src, root / ".claude" / "settings.json", force=force)
32
+
33
+
34
+ def ensure_root_from_templates(root: Path, *, force: bool = False) -> None:
35
+ """Copy root Makefile / TOOLS.md / .gitignore / make.bat from packaged templates."""
36
+ templates = paths.templates_dir()
37
+ for name in (".gitignore", "Makefile", "make.bat", "TOOLS.md"):
38
+ src = templates / name
39
+ if not src.is_file():
40
+ typer.echo(f"warn: missing template: {src}", err=True)
41
+ continue
42
+ copy_file(src, root / name, force=force)
@@ -1,61 +0,0 @@
1
- """Copy packaged Cursor/Claude skills & rules into a target repo."""
2
-
3
- from __future__ import annotations
4
-
5
- from pathlib import Path
6
-
7
- import typer
8
-
9
- from yjcli.modules import paths
10
- from yjcli.modules.fsutil import copy_file, copy_tree, ensure_real_dir
11
-
12
-
13
- def ensure_agent_wiring(root: Path, *, force: bool = False) -> None:
14
- """Install AGENTS/CLAUDE docs, skills, rules, and Claude settings."""
15
- templates = paths.templates_dir()
16
- agents_src = templates / "AGENTS.md"
17
- settings_src = templates / "settings.json"
18
- if not agents_src.is_file():
19
- raise FileNotFoundError(f"missing template: {agents_src}")
20
- if not settings_src.is_file():
21
- raise FileNotFoundError(f"missing template: {settings_src}")
22
-
23
- copy_file(agents_src, root / "AGENTS.md", force=force)
24
- copy_file(agents_src, root / "CLAUDE.md", force=force)
25
-
26
- cursor_skills = root / ".cursor" / "skills"
27
- claude_skills = root / ".claude" / "skills"
28
- cursor_rules = root / ".cursor" / "rules"
29
- claude_rules = root / ".claude" / "rules"
30
- ensure_real_dir(cursor_skills)
31
- ensure_real_dir(claude_skills)
32
- ensure_real_dir(cursor_rules)
33
- ensure_real_dir(claude_rules)
34
-
35
- skills_root = paths.skills_dir()
36
- if skills_root.is_dir():
37
- for skill_dir in sorted(p for p in skills_root.iterdir() if p.is_dir()):
38
- name = skill_dir.name
39
- copy_tree(skill_dir, cursor_skills / name, force=force)
40
- copy_tree(skill_dir, claude_skills / name, force=force)
41
-
42
- for rule_file in sorted(paths.cursor_rules_dir().glob("*")):
43
- if rule_file.is_file():
44
- copy_file(rule_file, cursor_rules / rule_file.name, force=force)
45
-
46
- for rule_file in sorted(paths.claude_rules_dir().glob("*")):
47
- if rule_file.is_file():
48
- copy_file(rule_file, claude_rules / rule_file.name, force=force)
49
-
50
- copy_file(settings_src, root / ".claude" / "settings.json", force=force)
51
-
52
-
53
- def ensure_root_from_templates(root: Path, *, force: bool = False) -> None:
54
- """Copy root Makefile / .gitignore / make.bat from packaged templates."""
55
- templates = paths.templates_dir()
56
- for name in (".gitignore", "Makefile", "make.bat"):
57
- src = templates / name
58
- if not src.is_file():
59
- typer.echo(f"warn: missing template: {src}", err=True)
60
- continue
61
- copy_file(src, root / name, force=force)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes