From 905901450a2dd89682b03533e8aef9957bb6a62e Mon Sep 17 00:00:00 2001 From: Song Seung Hu Date: Fri, 5 Jun 2026 17:12:25 +0900 Subject: [PATCH 1/2] fix CLI validation and repo hygiene --- .github/ISSUE_TEMPLATE/bug_report.md | 34 +++++++ .github/ISSUE_TEMPLATE/feature_request.md | 24 +++++ .github/pull_request_template.md | 13 +++ .github/workflows/ci.yml | 38 ++++++++ CHANGELOG.md | 12 +++ CONTRIBUTING.md | 23 +++++ SECURITY.md | 18 ++++ docs/spec.md | 103 ++++++++++++---------- package-lock.json | 19 ++++ package.json | 1 + src/index.js | 52 +++++++++-- tests/cli.test.js | 25 +++++- 12 files changed, 307 insertions(+), 55 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 package-lock.json diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..18f142f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Report a reproducible problem in codex-goal-parser +title: "bug: " +labels: bug +--- + +## Summary + +What happened? + +## Steps to reproduce + +1. +2. +3. + +## Expected behavior + +What did you expect to happen? + +## Actual behavior + +What happened instead? + +## Environment + +- Node version: +- npm version: +- codex-goal-parser version: + +## Additional context + +Any relevant command output, fixture, or input file. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..3a28b68 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest a planning, CLI, or repo-ingest improvement +title: "feat: " +labels: enhancement +--- + +## Problem + +What user problem should this solve? + +## Proposed behavior + +What should codex-goal-parser do? + +## Example input + +```bash +codex-goal-parser --objective "..." +``` + +## Expected output + +Describe the markdown or JSON behavior you expect. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..7822fa1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Summary + +- + +## Verification + +- [ ] `npm test` +- [ ] `npm run smoke` +- [ ] `npm pack --dry-run` + +## Notes + +Any follow-up work or known risk. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..926d571 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 18 + - 20 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install + run: npm ci + + - name: Test + run: npm test + + - name: Smoke test + run: npm run smoke + + - name: Verify package contents + run: npm pack --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c953e32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +## 0.2.0 + +- Completed the first reliability roadmap for repo-aware goal planning. +- Added JSON output with a documented contract. +- Added file-based planning inputs with `--issue-file` and `--context-file`. +- Improved repository context ingestion and validation hints. + +## 0.1.0 + +- Added the initial CLI for turning large software objectives into Codex `/goal` plans. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..60a5b30 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing + +Thanks for helping improve `codex-goal-parser`. + +## Local setup + +```bash +npm install +npm test +npm run smoke +``` + +## Development guidelines + +- Keep CLI behavior deterministic and dependency-light. +- Prefer small, verifiable changes. +- Add or update tests for behavior changes and bug fixes. +- Keep generated `/goal` commands compact enough to paste into Codex. +- Run `npm pack --dry-run` when changing package metadata or published files. + +## Pull requests + +Include a short summary, verification commands, and any remaining risks or follow-up work. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..8b9f170 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +The latest released version is the supported version. + +## Reporting a Vulnerability + +Please report security issues privately to the repository owner instead of opening a public issue. + +Include: + +- affected version or commit +- reproduction steps +- impact +- any relevant command output or input files + +The maintainer will review the report and coordinate a fix when appropriate. diff --git a/docs/spec.md b/docs/spec.md index 6335fc3..090b891 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -2,17 +2,44 @@ ## Goal -Convert a high-level software objective into a chain of smaller Codex `/goal` tasks. +Convert a high-level software objective into a short chain of smaller Codex `/goal` tasks that are clear, ordered, and verifiable. ## Inputs -- `objective`: the big thing the user wants -- `repo_context`: repository files, docs, metadata, or summaries -- `constraints` (optional): things not to change, time limits, required validation, etc. +- `--objective`: the large objective to decompose +- `--repo-context`: optional human-written repository context +- `--constraints`: optional boundaries, non-goals, or validation requirements +- `--repo-path`: optional repository path to inspect for context +- `--issue-file`: repeatable issue or task note file +- `--context-file`: repeatable supporting spec, architecture, or planning file +- `--output` / `--format`: `markdown` or `json` +- `--output-file`: optional file path for the generated plan + +If `--objective` is omitted, the CLI tries to derive one from the first issue or context file. + +## Repository Context + +When a repository path is available, the CLI summarizes lightweight project signals: + +- `README.md` +- `package.json` +- `pyproject.toml` +- `Makefile` +- top-level files +- source, test, docs, and deployment directories +- shallow directory tree +- common file extensions +- validation commands and test/config hints +- language and framework signals +- issue and context files supplied by the user + +The generated `/goal` commands use a compressed context summary so the command remains readable. ## Outputs -A plan with: +Markdown output is intended for humans. JSON output is intended for tools and follows the contract in `docs/output-contract.md`. + +Each generated plan includes: 1. `final_objective` 2. `sub_goals[]` @@ -25,53 +52,39 @@ A plan with: 3. `execution_order[]` 4. `notes[]` -## Rules - -- Produce 3-7 sub-goals by default -- Split again if a sub-goal has no clear done condition -- Prefer goals that fit one focused session -- Every sub-goal must have a validation method -- The generated `/goal` command must include a stop condition -- If the input is too vague, ask for one missing decision -- If the task is too small, recommend not using goal mode - -## Repository context sources - -Possible future inputs: - -- `README.md` -- `package.json` -- `pyproject.toml` -- issue text -- architecture notes -- test commands -- file tree summaries - -## First implementation options +## Planning Rules -### Option A — Prompt-first +- Produce 3-7 sub-goals by default. +- Preserve dependency order. +- Prefer one focused checkpoint per sub-goal. +- Include a done condition for every sub-goal. +- Include a validation method for every sub-goal. +- Include a stop condition in every generated `/goal` command. +- Narrow broad objectives to a first bounded slice before implementation. +- Keep unrelated follow-up work out of the current checkpoint. -Just generate high-quality decomposition prompts and examples. +## Objective Types -Pros: -- fast to ship -- useful immediately +The current implementation uses lightweight heuristics to choose one of these plan families: -Cons: -- less deterministic -- weaker structured output guarantees +- `migration` +- `refactor` +- `release` +- `stabilization` +- `documentation` +- `generic` -### Option B — Structured parser +The type affects the sub-goal templates and validation language. It does not execute code or call an LLM. -Build a small tool that accepts objective + repo summary and emits a typed plan. +## Error Handling -Pros: -- easier to integrate later -- more consistent outputs +The CLI should fail with a concise user-facing error when: -Cons: -- more design work upfront +- an option that requires a value is missing that value +- `--output` / `--format` is not `markdown` or `json` +- an explicit `--repo-path` does not exist +- an explicit `--repo-path` is not a directory -## Current direction +## Current Scope -Start with prompt-first artifacts and examples, then layer structured parsing if needed. +`codex-goal-parser` is a deterministic planning helper. It does not execute the generated goals, modify repositories, or call remote services. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7d94219 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,19 @@ +{ + "name": "codex-goal-parser", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codex-goal-parser", + "version": "0.2.0", + "license": "MIT", + "bin": { + "codex-goal-parser": "src/index.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json index 9f21bf2..df79409 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "files": [ "src", + "docs", "README.md", "LICENSE" ], diff --git a/src/index.js b/src/index.js index 9cf3c16..e46701c 100755 --- a/src/index.js +++ b/src/index.js @@ -17,38 +17,55 @@ function parseArgs(argv) { contextFiles: [], }; + function readRequiredValue(index, optionName) { + const value = argv[index + 1]; + if (!value || value.startsWith('-')) { + console.error(`Missing value for ${optionName}`); + process.exit(1); + } + return value; + } + for (let i = 0; i < argv.length; i += 1) { const arg = argv[i]; switch (arg) { case '--objective': case '-o': - args.objective = argv[++i] ?? ''; + args.objective = readRequiredValue(i, arg); + i += 1; break; case '--repo-context': case '-r': - args.repoContext = argv[++i] ?? ''; + args.repoContext = readRequiredValue(i, arg); + i += 1; break; case '--constraints': case '-c': - args.constraints = argv[++i] ?? ''; + args.constraints = readRequiredValue(i, arg); + i += 1; break; case '--repo-path': case '-p': - args.repoPath = argv[++i] ?? ''; + args.repoPath = readRequiredValue(i, arg); + i += 1; break; case '--format': case '-f': case '--output': - args.format = argv[++i] ?? 'markdown'; + args.format = readRequiredValue(i, arg); + i += 1; break; case '--output-file': - args.outputFile = argv[++i] ?? ''; + args.outputFile = readRequiredValue(i, arg); + i += 1; break; case '--issue-file': - args.issueFiles.push(argv[++i] ?? ''); + args.issueFiles.push(readRequiredValue(i, arg)); + i += 1; break; case '--context-file': - args.contextFiles.push(argv[++i] ?? ''); + args.contextFiles.push(readRequiredValue(i, arg)); + i += 1; break; case '--help': case '-h': @@ -333,6 +350,23 @@ function resolveRepoPath(args) { return looksLikeProjectRepo(process.cwd()) ? process.cwd() : ''; } +function validateRepoPath(repoPath) { + if (!repoPath) return ''; + + const resolved = path.resolve(repoPath); + if (!fs.existsSync(resolved)) { + console.error(`Repository path does not exist: ${resolved}`); + process.exit(1); + } + + if (!fs.statSync(resolved).isDirectory()) { + console.error(`Repository path must be a directory: ${resolved}`); + process.exit(1); + } + + return resolved; +} + function buildRepoContextFromPath(repoPath) { if (!repoPath) return ''; @@ -847,7 +881,7 @@ if (!['markdown', 'json'].includes(format)) { process.exit(1); } -const resolvedRepoPath = resolveRepoPath(args); +const resolvedRepoPath = validateRepoPath(resolveRepoPath(args)); const fileDerivedContext = buildRepoContextFromPath(resolvedRepoPath); const mergedRepoContext = [args.repoContext, ...externalPlanning.summaries, fileDerivedContext].filter(Boolean).join('. '); const plan = buildPlan(objective, mergedRepoContext, args.constraints, { diff --git a/tests/cli.test.js b/tests/cli.test.js index c667f44..8c7fad7 100644 --- a/tests/cli.test.js +++ b/tests/cli.test.js @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { execFileSync } from 'node:child_process'; +import { execFileSync, spawnSync } from 'node:child_process'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -20,6 +20,13 @@ function runJson(args, options = {}) { return JSON.parse(runCli([...args, '--output', 'json'], options)); } +function runCliResult(args, options = {}) { + return spawnSync('node', [cliPath, ...args], { + cwd: options.cwd || repoRoot, + encoding: 'utf8', + }); +} + test('json output follows the documented contract', () => { const plan = runJson(['--objective', 'Prepare this project for a safe public release.', '--repo-path', '.']); @@ -70,3 +77,19 @@ test('markdown output keeps the expected human-readable sections', () => { assert.match(output, /## Recommended sub-goals/); assert.match(output, /Command: `\/goal/); }); + +test('options that require values reject a following flag as missing input', () => { + const result = runCliResult(['--objective', '--output', 'json']); + + assert.equal(result.status, 1); + assert.match(result.stderr, /Missing value for --objective/); + assert.equal(result.stdout, ''); +}); + +test('repo path must be a directory', () => { + const result = runCliResult(['--objective', 'Check invalid repo path', '--repo-path', 'package.json', '--output', 'json']); + + assert.equal(result.status, 1); + assert.match(result.stderr, /Repository path must be a directory/); + assert.doesNotMatch(result.stderr, /ENOTDIR/); +}); From 49f24afcdd607c78d7cf7f12d645d5e0c26ed0f9 Mon Sep 17 00:00:00 2001 From: Song Seung Hu Date: Fri, 5 Jun 2026 17:14:46 +0900 Subject: [PATCH 2/2] fix CI test script glob --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index df79409..373f4a4 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,6 @@ "example": "node ./src/index.js --objective \"Migrate this old Node service to a cleaner TypeScript structure and make it safe to deploy.\" --repo-context \"Node service with package.json, README, and deployment scripts.\" --output markdown", "example:release": "node ./src/index.js --objective \"Prepare this project for a safe public release.\" --constraints \"Do not change runtime behavior unless needed.\" --output markdown", "smoke": "node ./src/index.js --objective \"Refactor this service safely.\" --repo-path . --output json", - "test": "node --test ./tests/**/*.test.js" + "test": "node --test tests/cli.test.js" } }