devlyn-cli 4.1.0 → 4.2.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 (72) hide show
  1. package/AGENTS.md +10 -94
  2. package/CLAUDE.md +7 -160
  3. package/LICENSE +21 -0
  4. package/README.md +65 -110
  5. package/bin/devlyn.js +95 -116
  6. package/bin/instruction-templates.json +43 -2
  7. package/bin/instructions.js +29 -8
  8. package/config/skills/_shared/adapters/claude.md +8 -85
  9. package/config/skills/_shared/adapters/codex.md +14 -34
  10. package/config/skills/_shared/adapters/omp.md +1 -11
  11. package/config/skills/_shared/codex-monitored.sh +4 -54
  12. package/config/skills/_shared/engine-doctor.sh +18 -58
  13. package/config/skills/_shared/engine-preflight.md +15 -47
  14. package/config/skills/_shared/expected-contract.py +242 -0
  15. package/config/skills/_shared/expected.schema.json +10 -95
  16. package/config/skills/_shared/invocation-receipt.py +26 -757
  17. package/config/skills/_shared/platform-support.py +37 -28
  18. package/config/skills/_shared/role-config.py +100 -421
  19. package/config/skills/_shared/runtime-principles.md +17 -93
  20. package/config/skills/_shared/task-complete.py +490 -285
  21. package/config/skills/_shared/task-completion.md +235 -0
  22. package/config/skills/devlyn-engines/SKILL.md +9 -11
  23. package/config/skills/devlyn-ideate/SKILL.md +57 -107
  24. package/config/skills/devlyn-ideate/references/elicitation.md +18 -99
  25. package/config/skills/devlyn-ideate/references/loop.md +110 -0
  26. package/config/skills/devlyn-ideate/references/package-format.md +128 -0
  27. package/config/skills/devlyn-ideate/scripts/acceptance.py +795 -0
  28. package/config/skills/devlyn-ideate/scripts/queue.py +1780 -0
  29. package/package.json +2 -3
  30. package/config/commit-conventions.md +0 -28
  31. package/config/skills/_shared/adapters/README.md +0 -74
  32. package/config/skills/_shared/adapters/grok.md +0 -100
  33. package/config/skills/_shared/archive_run.py +0 -969
  34. package/config/skills/_shared/codex-config.md +0 -81
  35. package/config/skills/_shared/collect-codex-findings.py +0 -505
  36. package/config/skills/_shared/finish-gate.py +0 -521
  37. package/config/skills/_shared/grok-anchor-guard.py +0 -78
  38. package/config/skills/_shared/judge-output-parser.py +0 -200
  39. package/config/skills/_shared/judge-role-evidence.py +0 -320
  40. package/config/skills/_shared/phase-prompt-render.py +0 -183
  41. package/config/skills/_shared/process-evidence.py +0 -926
  42. package/config/skills/_shared/resolve-bootstrap.py +0 -1410
  43. package/config/skills/_shared/resolve-stop-hook.py +0 -359
  44. package/config/skills/_shared/run-bounded.py +0 -58
  45. package/config/skills/_shared/spec-verify-check.py +0 -5083
  46. package/config/skills/_shared/state-phase-write.py +0 -5989
  47. package/config/skills/_shared/terminal-claim-check.py +0 -777
  48. package/config/skills/_shared/verify-merge-findings.py +0 -3528
  49. package/config/skills/devlyn-design-ui/SKILL.md +0 -650
  50. package/config/skills/devlyn-ideate/references/from-spec-mode.md +0 -61
  51. package/config/skills/devlyn-ideate/references/project-mode.md +0 -80
  52. package/config/skills/devlyn-ideate/references/spec-template.md +0 -121
  53. package/config/skills/devlyn-ideate/references/templates/decision.md +0 -48
  54. package/config/skills/devlyn-ideate/references/templates/roadmap.md +0 -50
  55. package/config/skills/devlyn-ideate/references/templates/vision.md +0 -44
  56. package/config/skills/devlyn-queue/SKILL.md +0 -66
  57. package/config/skills/devlyn-queue/scripts/append.py +0 -94
  58. package/config/skills/devlyn-resolve/SKILL.md +0 -379
  59. package/config/skills/devlyn-resolve/references/free-form-mode.md +0 -71
  60. package/config/skills/devlyn-resolve/references/outer-loop.md +0 -23
  61. package/config/skills/devlyn-resolve/references/phases/build-gate.md +0 -75
  62. package/config/skills/devlyn-resolve/references/phases/cleanup.md +0 -33
  63. package/config/skills/devlyn-resolve/references/phases/implement.md +0 -44
  64. package/config/skills/devlyn-resolve/references/phases/plan.md +0 -39
  65. package/config/skills/devlyn-resolve/references/phases/probe-derive.md +0 -261
  66. package/config/skills/devlyn-resolve/references/phases/surface-close.md +0 -34
  67. package/config/skills/devlyn-resolve/references/phases/verify.md +0 -310
  68. package/config/skills/devlyn-resolve/references/state-schema.md +0 -152
  69. package/config/skills/devlyn-resolve/references/task-completion.md +0 -160
  70. package/config/templates/prompt-templates.md +0 -71
  71. package/config/templates/template-feature.spec.md +0 -255
  72. package/config/templates/template-product-spec.md +0 -680
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import errno
7
- import json
8
7
  import os
9
8
  from pathlib import Path
10
9
  import re
@@ -62,8 +61,15 @@ def file_lock(path, *, blocking=False):
62
61
  fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
63
62
 
64
63
 
65
- def native_argv(argv):
66
- """Resolve supported npm engine shims to their package's Node bin, never cmd /c."""
64
+ SHIM_NODE = re.compile(r'IF EXIST "%(?:~dp0|dp0%)\\node\.exe"')
65
+ SHIM_RUN = re.compile(r'(?:"%_prog%"|"%~dp0\\node\.exe"|\bnode)\s+"%(?:~dp0|dp0%)\\([^"\r\n]+)"\s+%\*')
66
+ SHIM_SET = re.compile(r'(?im)^[ \t]*@?SET[ \t]+"?([^=" \t]+)=')
67
+ NPM_CLI = re.compile(r'SET "(NP[MX]_CLI_JS)=%~dp0\\([^"\r\n]+)"')
68
+
69
+
70
+ def native_argv(argv, cwd=None):
71
+ """Resolve argv as Windows does, for a command that will run in cwd; a Node command shim runs its Node and script
72
+ directly, never cmd /c."""
67
73
  if os.name != "nt":
68
74
  return list(argv)
69
75
  binary = shutil.which(argv[0])
@@ -72,33 +78,36 @@ def native_argv(argv):
72
78
  shim = Path(binary)
73
79
  if shim.suffix.lower() not in {".cmd", ".bat"}:
74
80
  return [binary, *argv[1:]]
75
- packages = {"codex": "@openai/codex", "claude": "@anthropic-ai/claude-code"}
76
- package = packages.get(shim.stem.lower())
77
- if shim.suffix.lower() != ".cmd" or package is None:
78
- raise OSError(f"unsupported native command shim: {shim}")
79
- root = (shim.parent.parent if shim.parent.name == ".bin" else shim.parent / "node_modules") / package
80
- try:
81
- manifest = json.loads((root / "package.json").read_text(encoding="utf-8"))
82
- except (ValueError, UnicodeError) as exc:
83
- raise OSError(f"invalid npm engine manifest: {shim}") from exc
84
- if not isinstance(manifest, dict):
85
- raise OSError(f"invalid npm engine manifest: {shim}")
86
- entry = manifest.get("bin")
87
- entry = entry.get(shim.stem.lower()) if isinstance(entry, dict) else entry
88
- if manifest.get("name") != package or not isinstance(entry, str):
89
- raise OSError(f"invalid npm engine bin: {shim}")
90
- target = (root / entry).resolve(strict=True)
91
- if not target.is_relative_to(root.resolve()) or target.suffix not in {".js", ".cjs", ".mjs"}:
92
- raise OSError(f"unsupported npm engine bin: {target}")
93
- raw = shim.read_text(encoding="utf-8")
94
- matches = re.findall(r'"%_prog%"\s+"%dp0%\\([^"\r\n]+)"\s+%\*', raw)
95
- if len(matches) != 1 or (shim.parent / matches[0]).resolve() != target:
96
- raise OSError(f"unrecognized npm engine shim: {shim}")
97
81
  node = shim.parent / "node.exe"
98
- binary = str(node) if node.is_file() else shutil.which("node.exe")
99
- if binary is None:
82
+ node = str(node) if node.is_file() else shutil.which("node.exe")
83
+ script = shim_script(shim, shim.read_text(encoding="utf-8", errors="replace"), node, cwd)
84
+ if script is None:
85
+ raise OSError(f"unsupported native command shim: {shim}; use argv with an explicit interpreter, such as node and the script")
86
+ if node is None:
100
87
  raise FileNotFoundError(f"node.exe unavailable for {shim}")
101
- return [binary, str(target), *argv[1:]]
88
+ return [node, str(script.resolve(strict=True)), *argv[1:]]
89
+
90
+
91
+ def shim_script(shim, raw, node, cwd=None):
92
+ """The script a Node command shim runs, as cmd.exe would resolve it, else None: the one target of an npm, pnpm
93
+ or corepack cmd-shim that sets no environment, or for Node.js's own npm.cmd and npx.cmd the global prefix's
94
+ npm when installed there, else the bundled one."""
95
+ targets = set(SHIM_RUN.findall(raw))
96
+ if SHIM_NODE.search(raw) and len(targets) == 1 and set(SHIM_SET.findall(raw)) <= {"dp0", "_prog", "PATHEXT"}:
97
+ return shim.parent / targets.pop().replace("\\", "/")
98
+ cli = NPM_CLI.search(raw)
99
+ if not cli or f'"%NODE_EXE%" "%{cli[1]}%" %*' not in raw:
100
+ return None
101
+ prefix_js = re.search(r'SET "NPM_PREFIX_JS=%~dp0\\([^"\r\n]+)"', raw)
102
+ global_cli = re.search(rf'SET "NPM_PREFIX_{cli[1]}=%%F\\([^"\r\n]+)"', raw)
103
+ if prefix_js and global_cli and node:
104
+ # As the shim's FOR /F does: the last nonempty line npm-prefix.js prints names the global prefix.
105
+ output = subprocess.run([node, str(shim.parent / prefix_js[1].replace("\\", "/"))], cwd=cwd, capture_output=True,
106
+ text=True, encoding="utf-8").stdout
107
+ prefix = [line for line in output.splitlines() if line]
108
+ if prefix and (installed := Path(prefix[-1]) / global_cli[1].replace("\\", "/")).is_file():
109
+ return installed
110
+ return shim.parent / cli[2].replace("\\", "/")
102
111
 
103
112
 
104
113
  def system_exit_code(code):