duaer-spec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.cursor/rules/agents-workflow.mdc +50 -0
  2. package/.cursor/rules/ai-ui-copy.mdc +12 -0
  3. package/.cursor/rules/duaer-spec.mdc +33 -0
  4. package/.cursor/skills/duaer-analyze/SKILL.md +259 -0
  5. package/.cursor/skills/duaer-checklist/SKILL.md +383 -0
  6. package/.cursor/skills/duaer-clarify/SKILL.md +291 -0
  7. package/.cursor/skills/duaer-constitution/SKILL.md +177 -0
  8. package/.cursor/skills/duaer-converge/SKILL.md +277 -0
  9. package/.cursor/skills/duaer-git-commit/SKILL.md +68 -0
  10. package/.cursor/skills/duaer-git-feature/SKILL.md +94 -0
  11. package/.cursor/skills/duaer-git-initialize/SKILL.md +54 -0
  12. package/.cursor/skills/duaer-git-remote/SKILL.md +50 -0
  13. package/.cursor/skills/duaer-git-validate/SKILL.md +54 -0
  14. package/.cursor/skills/duaer-implement/SKILL.md +226 -0
  15. package/.cursor/skills/duaer-plan/SKILL.md +166 -0
  16. package/.cursor/skills/duaer-specify/SKILL.md +345 -0
  17. package/.cursor/skills/duaer-tasks/SKILL.md +214 -0
  18. package/.cursor/skills/duaer-taskstoissues/SKILL.md +109 -0
  19. package/.duaer/extensions/.registry +23 -0
  20. package/.duaer/extensions/git/README.md +119 -0
  21. package/.duaer/extensions/git/commands/duaer.git.commit.md +63 -0
  22. package/.duaer/extensions/git/commands/duaer.git.feature.md +82 -0
  23. package/.duaer/extensions/git/commands/duaer.git.initialize.md +49 -0
  24. package/.duaer/extensions/git/commands/duaer.git.remote.md +45 -0
  25. package/.duaer/extensions/git/commands/duaer.git.validate.md +49 -0
  26. package/.duaer/extensions/git/config-template.yml +79 -0
  27. package/.duaer/extensions/git/extension.yml +142 -0
  28. package/.duaer/extensions/git/git-config.yml +79 -0
  29. package/.duaer/extensions/git/scripts/bash/auto-commit.sh +211 -0
  30. package/.duaer/extensions/git/scripts/bash/create-new-feature-branch.sh +626 -0
  31. package/.duaer/extensions/git/scripts/bash/git-common.sh +56 -0
  32. package/.duaer/extensions/git/scripts/bash/initialize-repo.sh +54 -0
  33. package/.duaer/extensions/git/scripts/powershell/auto-commit.ps1 +230 -0
  34. package/.duaer/extensions/git/scripts/powershell/create-new-feature-branch.ps1 +592 -0
  35. package/.duaer/extensions/git/scripts/powershell/git-common.ps1 +52 -0
  36. package/.duaer/extensions/git/scripts/powershell/initialize-repo.ps1 +69 -0
  37. package/.duaer/extensions/git/scripts/python/auto_commit.py +195 -0
  38. package/.duaer/extensions/git/scripts/python/create_new_feature_branch.py +634 -0
  39. package/.duaer/extensions/git/scripts/python/git_common.py +81 -0
  40. package/.duaer/extensions/git/scripts/python/initialize_repo.py +89 -0
  41. package/.duaer/extensions.yml +167 -0
  42. package/.duaer/init-options.json +9 -0
  43. package/.duaer/integration.json +15 -0
  44. package/.duaer/integrations/cursor-agent.manifest.json +17 -0
  45. package/.duaer/integrations/duaer.manifest.json +19 -0
  46. package/.duaer/memory/.constitution-template.json +4 -0
  47. package/.duaer/memory/constitution.md +37 -0
  48. package/.duaer/memory/project-context.md +24 -0
  49. package/.duaer/memory/testing.md +14 -0
  50. package/.duaer/scripts/bash/check-prerequisites.sh +243 -0
  51. package/.duaer/scripts/bash/common.sh +926 -0
  52. package/.duaer/scripts/bash/create-new-feature.sh +407 -0
  53. package/.duaer/scripts/bash/resolve-template.sh +57 -0
  54. package/.duaer/scripts/bash/setup-plan.sh +85 -0
  55. package/.duaer/scripts/bash/setup-tasks.sh +94 -0
  56. package/.duaer/templates/checklist-template.md +45 -0
  57. package/.duaer/templates/constitution-template.md +50 -0
  58. package/.duaer/templates/plan-template.md +113 -0
  59. package/.duaer/templates/spec-template.md +131 -0
  60. package/.duaer/templates/tasks-template.md +252 -0
  61. package/.duaer/workflows/duaer/workflow.yml +78 -0
  62. package/.duaer/workflows/workflow-registry.json +13 -0
  63. package/ADOPT.md +75 -0
  64. package/AGENTS.md +290 -0
  65. package/CHANGELOG.md +28 -0
  66. package/DUADER.md +57 -0
  67. package/LICENSE +21 -0
  68. package/README.md +74 -0
  69. package/bin/duaer.mjs +298 -0
  70. package/docs/agent/README.md +12 -0
  71. package/docs/agent/change-checklist.md +130 -0
  72. package/docs/agent/e2e-test-plan.md +33 -0
  73. package/docs/agent/workflow.md +127 -0
  74. package/docs/baseline.md +10 -0
  75. package/package.json +49 -0
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env pwsh
2
+ # Git extension: initialize-repo.ps1
3
+ # Initialize a Git repository with an initial commit.
4
+ # Customizable -- replace this script to add .gitignore templates,
5
+ # default branch config, git-flow, LFS, signing, etc.
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ # Find project root
9
+ function Find-ProjectRoot {
10
+ param([string]$StartDir)
11
+ $current = Resolve-Path $StartDir
12
+ while ($true) {
13
+ foreach ($marker in @('.duaer', '.git')) {
14
+ if (Test-Path (Join-Path $current $marker)) {
15
+ return $current
16
+ }
17
+ }
18
+ $parent = Split-Path $current -Parent
19
+ if ($parent -eq $current) { return $null }
20
+ $current = $parent
21
+ }
22
+ }
23
+
24
+ $repoRoot = Find-ProjectRoot -StartDir $PSScriptRoot
25
+ if (-not $repoRoot) { $repoRoot = Get-Location }
26
+ Set-Location $repoRoot
27
+
28
+ # Read commit message from extension config, fall back to default
29
+ $commitMsg = "[Duaer] Initial commit"
30
+ $configFile = Join-Path $repoRoot ".duaer/extensions/git/git-config.yml"
31
+ if (Test-Path $configFile) {
32
+ foreach ($line in Get-Content $configFile) {
33
+ if ($line -match '^init_commit_message:\s*(.+)$') {
34
+ $val = $matches[1].Trim() -replace '^["'']' -replace '["'']$'
35
+ if ($val) { $commitMsg = $val }
36
+ break
37
+ }
38
+ }
39
+ }
40
+
41
+ # Check if git is available
42
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
43
+ Write-Warning "[specify] Warning: Git not found; skipped repository initialization"
44
+ exit 0
45
+ }
46
+
47
+ # Check if already a git repo
48
+ try {
49
+ git rev-parse --is-inside-work-tree 2>$null | Out-Null
50
+ if ($LASTEXITCODE -eq 0) {
51
+ Write-Warning "[specify] Git repository already initialized; skipping"
52
+ exit 0
53
+ }
54
+ } catch { }
55
+
56
+ # Initialize
57
+ try {
58
+ $out = git init -q 2>&1 | Out-String
59
+ if ($LASTEXITCODE -ne 0) { throw "git init failed: $out" }
60
+ $out = git add . 2>&1 | Out-String
61
+ if ($LASTEXITCODE -ne 0) { throw "git add failed: $out" }
62
+ $out = git commit --allow-empty -q -m $commitMsg 2>&1 | Out-String
63
+ if ($LASTEXITCODE -ne 0) { throw "git commit failed: $out" }
64
+ } catch {
65
+ Write-Warning "[specify] Error: $_"
66
+ exit 1
67
+ }
68
+
69
+ Write-Host "[OK] Git repository initialized"
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """Git extension: auto_commit.py
3
+
4
+ Automatically commit changes after a Duaer command completes.
5
+ Python port of ``auto-commit.sh`` / ``auto-commit.ps1``.
6
+ Checks per-command config keys in git-config.yml before committing.
7
+
8
+ Usage: auto_commit.py <event_name>
9
+ e.g.: auto_commit.py after_specify
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+
20
+
21
+ def _find_project_root(start: Path) -> Path | None:
22
+ current = start
23
+ while True:
24
+ if (current / ".duaer").is_dir() or (current / ".git").exists():
25
+ return current
26
+ if current.parent == current:
27
+ return None
28
+ current = current.parent
29
+
30
+
31
+ def _value_after_colon(line: str) -> str:
32
+ return re.sub(r"^[^:]*:\s*", "", line)
33
+
34
+
35
+ def _strip_quotes(value: str) -> str:
36
+ """Strip surrounding whitespace, then one leading quote and all trailing quotes.
37
+
38
+ Trimming first matters when the YAML value has trailing whitespace after a
39
+ closing quote (``message: "Done" ``): stripping quotes anchored to the end
40
+ of string would leave the closing quote dangling (``Done" ``) because the
41
+ quote is no longer at the end. The PowerShell twin ``.Trim()``s before
42
+ stripping, so trim here too to keep all three script variants in parity.
43
+ """
44
+ value = value.strip()
45
+ value = re.sub(r"^[\"']", "", value)
46
+ return re.sub(r"[\"']*$", "", value)
47
+
48
+
49
+ def _parse_auto_commit_config(
50
+ config_file: Path, event_name: str
51
+ ) -> tuple[bool, str]:
52
+ """Parse the auto_commit section for this event, mirroring the bash line parser.
53
+
54
+ Returns (enabled, commit_msg). Looks for auto_commit.<event_name>.enabled
55
+ and .message, with auto_commit.default as fallback.
56
+ """
57
+ enabled = False
58
+ commit_msg = ""
59
+ default_enabled = False
60
+ in_auto_commit = False
61
+ in_event = False
62
+
63
+ try:
64
+ content = config_file.read_text(encoding="utf-8")
65
+ except (OSError, UnicodeDecodeError):
66
+ # Unreadable or non-UTF-8 config is treated like a missing one:
67
+ # auto-commit stays disabled instead of crashing with a traceback.
68
+ return False, ""
69
+ for record in content.splitlines(keepends=True):
70
+ if not record.endswith("\n"):
71
+ break
72
+ line = record[:-1]
73
+ if line.startswith("auto_commit:"):
74
+ in_auto_commit = True
75
+ in_event = False
76
+ continue
77
+
78
+ # Exit auto_commit section on next top-level key
79
+ if in_auto_commit and re.match(r"^[a-z]", line):
80
+ break
81
+
82
+ if not in_auto_commit:
83
+ continue
84
+
85
+ if re.match(r"^\s+default:\s", line):
86
+ value = re.sub(r"\s", "", _value_after_colon(line)).lower()
87
+ if value == "true":
88
+ default_enabled = True
89
+
90
+ if re.match(rf"^\s+{re.escape(event_name)}:", line):
91
+ in_event = True
92
+ continue
93
+
94
+ if in_event:
95
+ # Exit on next sibling key (same indent level as event name)
96
+ if re.match(r"^\s{2}[a-z]", line) and not re.match(r"^\s{4}", line):
97
+ in_event = False
98
+ continue
99
+ if re.search(r"\s+enabled:", line):
100
+ value = re.sub(r"\s", "", _value_after_colon(line)).lower()
101
+ if value == "true":
102
+ enabled = True
103
+ elif value == "false":
104
+ enabled = False
105
+ if re.search(r"\s+message:", line):
106
+ commit_msg = _strip_quotes(_value_after_colon(line))
107
+
108
+ # If event-specific key not found, use default — but only if the event
109
+ # section didn't exist at all (an explicit false must win).
110
+ if not enabled and default_enabled:
111
+ if not re.search(rf"^\s*{re.escape(event_name)}:", content, re.MULTILINE):
112
+ enabled = True
113
+
114
+ return enabled, commit_msg
115
+
116
+
117
+ def main(argv: list[str]) -> int:
118
+ event_name = argv[0] if argv else ""
119
+ if not event_name:
120
+ print(f"Usage: {Path(sys.argv[0]).name} <event_name>", file=sys.stderr)
121
+ return 1
122
+
123
+ script_dir = Path(__file__).resolve().parent
124
+ repo_root = _find_project_root(script_dir) or Path.cwd()
125
+
126
+ if shutil.which("git") is None:
127
+ print("[specify] Warning: Git not found; skipped auto-commit", file=sys.stderr)
128
+ return 0
129
+
130
+ probe = subprocess.run(
131
+ ["git", "rev-parse", "--is-inside-work-tree"],
132
+ cwd=repo_root,
133
+ capture_output=True,
134
+ text=True,
135
+ )
136
+ if probe.returncode != 0:
137
+ print(
138
+ "[specify] Warning: Not a Git repository; skipped auto-commit",
139
+ file=sys.stderr,
140
+ )
141
+ return 0
142
+
143
+ config_file = repo_root / ".duaer" / "extensions" / "git" / "git-config.yml"
144
+ if not config_file.is_file():
145
+ # No config file — auto-commit disabled by default
146
+ return 0
147
+
148
+ enabled, commit_msg = _parse_auto_commit_config(config_file, event_name)
149
+ if not enabled:
150
+ return 0
151
+
152
+ # Check if there are changes to commit
153
+ def _quiet(*args: str) -> bool:
154
+ return (
155
+ subprocess.run(
156
+ ["git", *args], cwd=repo_root, capture_output=True, text=True
157
+ ).returncode
158
+ == 0
159
+ )
160
+
161
+ untracked = subprocess.run(
162
+ ["git", "ls-files", "--others", "--exclude-standard"],
163
+ cwd=repo_root,
164
+ capture_output=True,
165
+ text=True,
166
+ ).stdout.strip()
167
+ if _quiet("diff", "--quiet", "HEAD") and _quiet("diff", "--cached", "--quiet") and not untracked:
168
+ print(f"[specify] No changes to commit after {event_name}", file=sys.stderr)
169
+ return 0
170
+
171
+ # Derive a human-readable command name from the event
172
+ # e.g., after_specify -> specify, before_plan -> plan
173
+ command_name = re.sub(r"^(after_|before_)", "", event_name)
174
+ phase = "before" if event_name.startswith("before_") else "after"
175
+
176
+ if not commit_msg:
177
+ commit_msg = f"[Duaer] Auto-commit {phase} {command_name}"
178
+
179
+ steps = [
180
+ (["git", "add", "."], "git add"),
181
+ (["git", "commit", "-q", "-m", commit_msg], "git commit"),
182
+ ]
183
+ for cmd, label in steps:
184
+ result = subprocess.run(cmd, cwd=repo_root, capture_output=True, text=True)
185
+ if result.returncode != 0:
186
+ output = (result.stdout + result.stderr).strip()
187
+ print(f"[specify] Error: {label} failed: {output}", file=sys.stderr)
188
+ return 1
189
+
190
+ print(f"[OK] Changes committed {phase} {command_name}", file=sys.stderr)
191
+ return 0
192
+
193
+
194
+ if __name__ == "__main__":
195
+ raise SystemExit(main(sys.argv[1:]))