nubos-pilot 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.
- package/agents/np-ai-researcher.md +140 -0
- package/agents/np-code-fixer.md +363 -0
- package/agents/np-code-reviewer.md +351 -0
- package/agents/np-domain-researcher.md +136 -0
- package/agents/np-eval-auditor.md +167 -0
- package/agents/np-eval-planner.md +153 -0
- package/agents/np-executor.md +72 -0
- package/agents/np-framework-selector.md +171 -0
- package/agents/np-nyquist-auditor.md +185 -0
- package/agents/np-plan-checker.md +165 -0
- package/agents/np-planner.md +199 -0
- package/agents/np-researcher.md +150 -0
- package/agents/np-security-auditor.md +206 -0
- package/agents/np-ui-auditor.md +369 -0
- package/agents/np-ui-checker.md +192 -0
- package/agents/np-ui-researcher.md +324 -0
- package/agents/np-verifier.md +79 -0
- package/bin/check-coverage.cjs +40 -0
- package/bin/check-workflows.cjs +171 -0
- package/bin/check-workflows.test.cjs +208 -0
- package/bin/install.js +500 -0
- package/bin/np-tools/_commands.cjs +70 -0
- package/bin/np-tools/add-tests.cjs +171 -0
- package/bin/np-tools/add-tests.test.cjs +122 -0
- package/bin/np-tools/add-todo.cjs +108 -0
- package/bin/np-tools/add-todo.test.cjs +112 -0
- package/bin/np-tools/agent-skills.cjs +14 -0
- package/bin/np-tools/agent-skills.test.cjs +42 -0
- package/bin/np-tools/ai-integration-phase.cjs +109 -0
- package/bin/np-tools/ai-integration-phase.test.cjs +123 -0
- package/bin/np-tools/askuser.cjs +53 -0
- package/bin/np-tools/askuser.test.cjs +49 -0
- package/bin/np-tools/autonomous.cjs +69 -0
- package/bin/np-tools/autonomous.test.cjs +74 -0
- package/bin/np-tools/checkpoint.cjs +101 -0
- package/bin/np-tools/checkpoint.test.cjs +119 -0
- package/bin/np-tools/code-review.cjs +133 -0
- package/bin/np-tools/code-review.test.cjs +96 -0
- package/bin/np-tools/commit-task.cjs +120 -0
- package/bin/np-tools/commit-task.test.cjs +160 -0
- package/bin/np-tools/commit.cjs +103 -0
- package/bin/np-tools/commit.test.cjs +93 -0
- package/bin/np-tools/config.cjs +101 -0
- package/bin/np-tools/config.test.cjs +71 -0
- package/bin/np-tools/discuss-phase-power.cjs +265 -0
- package/bin/np-tools/discuss-phase-power.test.cjs +242 -0
- package/bin/np-tools/discuss-phase.cjs +132 -0
- package/bin/np-tools/discuss-phase.test.cjs +148 -0
- package/bin/np-tools/dispatch.cjs +116 -0
- package/bin/np-tools/doctor.cjs +242 -0
- package/bin/np-tools/eval-review.cjs +116 -0
- package/bin/np-tools/eval-review.test.cjs +123 -0
- package/bin/np-tools/execute-phase.cjs +182 -0
- package/bin/np-tools/execute-phase.test.cjs +116 -0
- package/bin/np-tools/execute-plan.cjs +124 -0
- package/bin/np-tools/execute-plan.test.cjs +82 -0
- package/bin/np-tools/help.cjs +28 -0
- package/bin/np-tools/help.test.cjs +29 -0
- package/bin/np-tools/init-dispatch.test.cjs +91 -0
- package/bin/np-tools/metrics.cjs +97 -0
- package/bin/np-tools/metrics.test.cjs +188 -0
- package/bin/np-tools/new-milestone.cjs +288 -0
- package/bin/np-tools/new-milestone.test.cjs +166 -0
- package/bin/np-tools/new-project.cjs +284 -0
- package/bin/np-tools/new-project.test.cjs +165 -0
- package/bin/np-tools/next.cjs +7 -0
- package/bin/np-tools/next.test.cjs +30 -0
- package/bin/np-tools/park.cjs +48 -0
- package/bin/np-tools/park.test.cjs +50 -0
- package/bin/np-tools/pause-work.cjs +24 -0
- package/bin/np-tools/pause-work.test.cjs +74 -0
- package/bin/np-tools/phase.cjs +71 -0
- package/bin/np-tools/phase.test.cjs +81 -0
- package/bin/np-tools/plan-diff.cjs +57 -0
- package/bin/np-tools/plan-diff.test.cjs +134 -0
- package/bin/np-tools/plan-milestone-gaps.cjs +115 -0
- package/bin/np-tools/plan-milestone-gaps.test.cjs +122 -0
- package/bin/np-tools/plan-phase.cjs +350 -0
- package/bin/np-tools/plan-phase.test.cjs +263 -0
- package/bin/np-tools/progress.cjs +7 -0
- package/bin/np-tools/progress.test.cjs +44 -0
- package/bin/np-tools/queue.cjs +213 -0
- package/bin/np-tools/research-phase.cjs +144 -0
- package/bin/np-tools/research-phase.test.cjs +154 -0
- package/bin/np-tools/reset-slice.cjs +17 -0
- package/bin/np-tools/reset-slice.test.cjs +96 -0
- package/bin/np-tools/resolve-model.cjs +110 -0
- package/bin/np-tools/resolve-model.test.cjs +200 -0
- package/bin/np-tools/resume-work.cjs +76 -0
- package/bin/np-tools/resume-work.test.cjs +91 -0
- package/bin/np-tools/skip.cjs +48 -0
- package/bin/np-tools/skip.test.cjs +66 -0
- package/bin/np-tools/slug.cjs +34 -0
- package/bin/np-tools/slug.test.cjs +46 -0
- package/bin/np-tools/state.cjs +16 -0
- package/bin/np-tools/state.test.cjs +40 -0
- package/bin/np-tools/stats.cjs +151 -0
- package/bin/np-tools/stats.test.cjs +118 -0
- package/bin/np-tools/triage.cjs +128 -0
- package/bin/np-tools/ui-phase.cjs +108 -0
- package/bin/np-tools/ui-phase.test.cjs +121 -0
- package/bin/np-tools/ui-review.cjs +108 -0
- package/bin/np-tools/ui-review.test.cjs +120 -0
- package/bin/np-tools/undo-task.cjs +31 -0
- package/bin/np-tools/undo-task.test.cjs +117 -0
- package/bin/np-tools/undo.cjs +43 -0
- package/bin/np-tools/undo.test.cjs +120 -0
- package/bin/np-tools/unpark.cjs +48 -0
- package/bin/np-tools/unpark.test.cjs +50 -0
- package/bin/np-tools/verify-work.cjs +186 -0
- package/bin/np-tools/verify-work.test.cjs +97 -0
- package/docs/adr/0001-no-daemon-invariant.md +82 -0
- package/docs/adr/0002-zero-runtime-dependencies.md +90 -0
- package/docs/adr/0003-max-six-unit-types.md +85 -0
- package/docs/adr/0004-atomic-commit-per-unit.md +102 -0
- package/docs/adr/0005-three-orthogonal-file-trees.md +98 -0
- package/docs/adr/0006-yaml-dependency-amendment.md +60 -0
- package/docs/adr/README.md +27 -0
- package/docs/agent-frontmatter-schema.md +84 -0
- package/docs/phase-artifact-schemas.md +292 -0
- package/docs/phase-directory-layout.md +82 -0
- package/lib/__tests__/README.md +1 -0
- package/lib/agents.cjs +98 -0
- package/lib/agents.test.cjs +286 -0
- package/lib/askuser.cjs +36 -0
- package/lib/askuser.test.cjs +310 -0
- package/lib/checkpoint.cjs +135 -0
- package/lib/checkpoint.test.cjs +184 -0
- package/lib/core.cjs +165 -0
- package/lib/core.test.cjs +405 -0
- package/lib/fixtures/README.md +1 -0
- package/lib/fixtures/phase-tree/README.md +1 -0
- package/lib/fixtures/plans/cycle/PLAN.md +16 -0
- package/lib/fixtures/plans/cycle/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/cycle/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/cycle/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/linear/PLAN.md +16 -0
- package/lib/fixtures/plans/linear/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/linear/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/linear/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/parallel/PLAN.md +16 -0
- package/lib/fixtures/plans/parallel/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/parallel/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/parallel/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/wave-conflict/PLAN.md +16 -0
- package/lib/fixtures/plans/wave-conflict/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/wave-conflict/tasks/T-02.md +20 -0
- package/lib/fixtures/roadmap/ROADMAP-malformed.md +3 -0
- package/lib/fixtures/roadmap/ROADMAP-minimal.md +51 -0
- package/lib/fixtures/roadmap/roadmap-malformed.yaml +7 -0
- package/lib/fixtures/roadmap/roadmap-minimal.yaml +40 -0
- package/lib/fixtures/roadmap/roadmap-ten-phases.yaml +101 -0
- package/lib/fixtures/templates/phase-context.md +6 -0
- package/lib/fixtures/templates/plan-skeleton.md +6 -0
- package/lib/frontmatter.cjs +251 -0
- package/lib/frontmatter.test.cjs +177 -0
- package/lib/gaps.cjs +197 -0
- package/lib/gaps.test.cjs +200 -0
- package/lib/git.cjs +207 -0
- package/lib/git.test.cjs +305 -0
- package/lib/install/agents-md.cjs +77 -0
- package/lib/install/backup.cjs +70 -0
- package/lib/install/codex-toml.cjs +440 -0
- package/lib/install/managed-block.cjs +30 -0
- package/lib/install/manifest.cjs +148 -0
- package/lib/install/mcp-writer.cjs +127 -0
- package/lib/install/runtime-detect.cjs +44 -0
- package/lib/install/staging.cjs +149 -0
- package/lib/metrics-aggregate.cjs +229 -0
- package/lib/metrics-aggregate.test.cjs +192 -0
- package/lib/metrics.cjs +120 -0
- package/lib/metrics.test.cjs +182 -0
- package/lib/model-aliases.regression.test.cjs +16 -0
- package/lib/model-profiles.cjs +42 -0
- package/lib/model-profiles.test.cjs +61 -0
- package/lib/next.cjs +236 -0
- package/lib/next.test.cjs +194 -0
- package/lib/phase.cjs +95 -0
- package/lib/phase.test.cjs +189 -0
- package/lib/plan-checker-contract.test.cjs +72 -0
- package/lib/plan-diff.cjs +173 -0
- package/lib/plan-diff.test.cjs +217 -0
- package/lib/plan.cjs +85 -0
- package/lib/plan.test.cjs +263 -0
- package/lib/progress.cjs +95 -0
- package/lib/progress.test.cjs +116 -0
- package/lib/researcher-contract.test.cjs +61 -0
- package/lib/roadmap-render.cjs +206 -0
- package/lib/roadmap-render.test.cjs +121 -0
- package/lib/roadmap.cjs +416 -0
- package/lib/roadmap.test.cjs +371 -0
- package/lib/runtime/_contract.test.cjs +61 -0
- package/lib/runtime/_readline.cjs +119 -0
- package/lib/runtime/_readline.test.cjs +126 -0
- package/lib/runtime/claude.cjs +48 -0
- package/lib/runtime/claude.test.cjs +101 -0
- package/lib/runtime/codex.cjs +35 -0
- package/lib/runtime/codex.test.cjs +114 -0
- package/lib/runtime/gemini.cjs +35 -0
- package/lib/runtime/gemini.test.cjs +109 -0
- package/lib/runtime/index.cjs +49 -0
- package/lib/runtime/index.test.cjs +181 -0
- package/lib/runtime/opencode.cjs +35 -0
- package/lib/runtime/opencode.test.cjs +124 -0
- package/lib/state.cjs +205 -0
- package/lib/state.test.cjs +264 -0
- package/lib/surface-audit.test.cjs +46 -0
- package/lib/tasks.cjs +327 -0
- package/lib/tasks.test.cjs +389 -0
- package/lib/template.cjs +66 -0
- package/lib/template.test.cjs +159 -0
- package/lib/undo.cjs +179 -0
- package/lib/undo.test.cjs +261 -0
- package/lib/verify.cjs +116 -0
- package/lib/verify.test.cjs +187 -0
- package/np-tools.cjs +303 -0
- package/package.json +39 -0
- package/templates/AI-SPEC.md +90 -0
- package/templates/CONTEXT.md +32 -0
- package/templates/PLAN.md +69 -0
- package/templates/PROJECT.md +60 -0
- package/templates/REQUIREMENTS.md +38 -0
- package/templates/SECURITY.md +61 -0
- package/templates/UI-SPEC.md +64 -0
- package/templates/VALIDATION.md +76 -0
- package/templates/claude/payload/README.md +11 -0
- package/templates/opencode/opencode.json +6 -0
- package/templates/opencode/payload/AGENTS.md +9 -0
- package/workflows/add-backlog.md +212 -0
- package/workflows/add-tests.md +69 -0
- package/workflows/add-todo.md +222 -0
- package/workflows/ai-integration-phase.md +230 -0
- package/workflows/autonomous.md +94 -0
- package/workflows/cleanup.md +325 -0
- package/workflows/code-review-fix.md +435 -0
- package/workflows/code-review.md +447 -0
- package/workflows/discuss-phase-assumptions.md +269 -0
- package/workflows/discuss-phase-power.md +139 -0
- package/workflows/discuss-phase.md +386 -0
- package/workflows/dispatch.md +9 -0
- package/workflows/doctor.md +10 -0
- package/workflows/eval-review.md +243 -0
- package/workflows/execute-phase.md +142 -0
- package/workflows/execute-plan.md +82 -0
- package/workflows/help.md +8 -0
- package/workflows/new-milestone.md +166 -0
- package/workflows/new-project.md +213 -0
- package/workflows/next.md +8 -0
- package/workflows/note.md +244 -0
- package/workflows/park.md +29 -0
- package/workflows/pause-work.md +34 -0
- package/workflows/plan-milestone-gaps.md +233 -0
- package/workflows/plan-phase.md +351 -0
- package/workflows/progress.md +8 -0
- package/workflows/queue.md +9 -0
- package/workflows/research-phase.md +327 -0
- package/workflows/reset-slice.md +39 -0
- package/workflows/resume-work.md +79 -0
- package/workflows/review.md +489 -0
- package/workflows/secure-phase.md +209 -0
- package/workflows/session-report.md +243 -0
- package/workflows/skip.md +29 -0
- package/workflows/state.md +7 -0
- package/workflows/stats.md +170 -0
- package/workflows/thread.md +214 -0
- package/workflows/triage.md +9 -0
- package/workflows/ui-phase.md +246 -0
- package/workflows/ui-review.md +222 -0
- package/workflows/undo-task.md +42 -0
- package/workflows/undo.md +55 -0
- package/workflows/unpark.md +29 -0
- package/workflows/validate-phase.md +231 -0
- package/workflows/verify-work.md +83 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { getPhase } = require('../../lib/roadmap.cjs');
|
|
5
|
+
const { paddedPhase, phaseSlug, findPhaseDir } = require('../../lib/phase.cjs');
|
|
6
|
+
const { detect } = require('../../lib/runtime/index.cjs');
|
|
7
|
+
|
|
8
|
+
const VALID_DEPTHS = new Set(['quick', 'standard', 'deep']);
|
|
9
|
+
|
|
10
|
+
function _usage() {
|
|
11
|
+
return 'Usage: np-tools.cjs init code-review <phase> [--depth=quick|standard|deep]';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function _validatePhaseArg(raw) {
|
|
15
|
+
if (raw == null || raw === '') {
|
|
16
|
+
throw new NubosPilotError(
|
|
17
|
+
'code-review-invalid-arg',
|
|
18
|
+
'code-review requires a phase number argument',
|
|
19
|
+
{ value: raw == null ? '' : String(raw) },
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const s = String(raw);
|
|
23
|
+
if (!/^\d+(\.\d+)?$/.test(s)) {
|
|
24
|
+
throw new NubosPilotError(
|
|
25
|
+
'code-review-invalid-arg',
|
|
26
|
+
'Invalid phase number: ' + s,
|
|
27
|
+
{ value: s },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function _parseDepth(args) {
|
|
34
|
+
let depth = 'standard';
|
|
35
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
36
|
+
const s = String(args[i]);
|
|
37
|
+
if (s.startsWith('--depth=')) {
|
|
38
|
+
depth = s.slice('--depth='.length);
|
|
39
|
+
} else if (s === '--depth' && args[i + 1]) {
|
|
40
|
+
depth = String(args[i + 1]);
|
|
41
|
+
i += 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!VALID_DEPTHS.has(depth)) {
|
|
45
|
+
throw new NubosPilotError(
|
|
46
|
+
'code-review-invalid-depth',
|
|
47
|
+
'depth must be one of quick|standard|deep, got: ' + depth,
|
|
48
|
+
{ depth },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return depth;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function _resolvePhaseDir(phaseArg, cwd, slug) {
|
|
55
|
+
const hit = findPhaseDir(phaseArg, cwd);
|
|
56
|
+
if (hit) return hit;
|
|
57
|
+
const padded = paddedPhase(phaseArg);
|
|
58
|
+
return path.join(path.resolve(cwd), '.nubos-pilot', 'phases', padded + '-' + slug);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function _buildPayload(phaseArg, cwd, depth) {
|
|
62
|
+
let phase;
|
|
63
|
+
try {
|
|
64
|
+
phase = getPhase(phaseArg, cwd);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err && err.code === 'phase-not-found') {
|
|
67
|
+
throw new NubosPilotError(
|
|
68
|
+
'code-review-not-found',
|
|
69
|
+
'Phase ' + phaseArg + ' not found in roadmap',
|
|
70
|
+
{ number: phaseArg },
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
const padded = paddedPhase(phaseArg);
|
|
76
|
+
const slug = phase.slug || phaseSlug(phase.name);
|
|
77
|
+
const phase_dir = _resolvePhaseDir(phaseArg, cwd, slug);
|
|
78
|
+
const review_path = path.join(phase_dir, padded + '-REVIEW.md');
|
|
79
|
+
const review_fix_path = path.join(phase_dir, padded + '-REVIEW-FIX.md');
|
|
80
|
+
const summary_path = path.join(phase_dir, padded + '-SUMMARY.md');
|
|
81
|
+
const summary_present = fs.existsSync(summary_path);
|
|
82
|
+
const has_review = fs.existsSync(review_path);
|
|
83
|
+
const { runtime } = detect({ cwd });
|
|
84
|
+
return {
|
|
85
|
+
_workflow: 'code-review',
|
|
86
|
+
phase: phaseArg,
|
|
87
|
+
padded,
|
|
88
|
+
phase_dir,
|
|
89
|
+
review_path,
|
|
90
|
+
review_fix_path,
|
|
91
|
+
summary_present,
|
|
92
|
+
summary_path,
|
|
93
|
+
has_review,
|
|
94
|
+
depth,
|
|
95
|
+
agents: { code_reviewer: 'np-code-reviewer', code_fixer: 'np-code-fixer' },
|
|
96
|
+
runtime,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _emitError(err, stderr) {
|
|
101
|
+
const code = err && err.name === 'NubosPilotError' ? err.code : 'code-review-internal-error';
|
|
102
|
+
const message = (err && err.message) || String(err);
|
|
103
|
+
const details = (err && err.details) || null;
|
|
104
|
+
stderr.write(JSON.stringify({ code, message, details }) + '\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function run(args, ctx) {
|
|
108
|
+
const context = ctx || {};
|
|
109
|
+
const cwd = context.cwd || process.cwd();
|
|
110
|
+
const stdout = context.stdout || process.stdout;
|
|
111
|
+
const stderr = context.stderr || process.stderr;
|
|
112
|
+
const list = Array.isArray(args) ? args : [];
|
|
113
|
+
if (list[0] == null || list[0] === '') {
|
|
114
|
+
stderr.write(_usage() + '\n');
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const phaseArg = _validatePhaseArg(list[0]);
|
|
119
|
+
const depth = _parseDepth(list.slice(1));
|
|
120
|
+
const payload = _buildPayload(phaseArg, cwd, depth);
|
|
121
|
+
stdout.write(JSON.stringify(payload, null, 2));
|
|
122
|
+
return 0;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
_emitError(err, stderr);
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { run, _buildPayload, _parseDepth };
|
|
130
|
+
|
|
131
|
+
if (require.main === module) {
|
|
132
|
+
process.exit(run(process.argv.slice(2)));
|
|
133
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { test } = require('node:test');
|
|
5
|
+
const assert = require('node:assert/strict');
|
|
6
|
+
const { Writable } = require('node:stream');
|
|
7
|
+
|
|
8
|
+
const codeReviewCli = require('./code-review.cjs');
|
|
9
|
+
|
|
10
|
+
const _sandboxes = [];
|
|
11
|
+
|
|
12
|
+
function makeSink() {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
const w = new Writable({
|
|
15
|
+
write(chunk, _enc, cb) { chunks.push(chunk); cb(); },
|
|
16
|
+
});
|
|
17
|
+
w.toString = () => Buffer.concat(chunks.map((c) => Buffer.isBuffer(c) ? c : Buffer.from(String(c)))).toString('utf-8');
|
|
18
|
+
return w;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function makeSandbox(yaml) {
|
|
22
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-code-review-'));
|
|
23
|
+
fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
|
|
24
|
+
fs.writeFileSync(path.join(root, '.nubos-pilot', 'roadmap.yaml'), yaml);
|
|
25
|
+
_sandboxes.push(root);
|
|
26
|
+
return root;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test.afterEach(() => {
|
|
30
|
+
while (_sandboxes.length) {
|
|
31
|
+
try { fs.rmSync(_sandboxes.pop(), { recursive: true, force: true }); } catch { }
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const DEMO_YAML = [
|
|
36
|
+
'milestones:',
|
|
37
|
+
' - id: v1.0',
|
|
38
|
+
' name: v1',
|
|
39
|
+
' phases:',
|
|
40
|
+
' - number: 10',
|
|
41
|
+
' name: Review Utility',
|
|
42
|
+
' slug: review-utility',
|
|
43
|
+
' status: in-progress',
|
|
44
|
+
].join('\n') + '\n';
|
|
45
|
+
|
|
46
|
+
test('CR-1: happy path emits code-review payload with default depth standard', () => {
|
|
47
|
+
const sb = makeSandbox(DEMO_YAML);
|
|
48
|
+
const stdout = makeSink();
|
|
49
|
+
const stderr = makeSink();
|
|
50
|
+
const code = codeReviewCli.run(['10'], { cwd: sb, stdout, stderr });
|
|
51
|
+
assert.equal(code, 0, 'stderr=' + stderr.toString());
|
|
52
|
+
const parsed = JSON.parse(stdout.toString());
|
|
53
|
+
assert.equal(parsed._workflow, 'code-review');
|
|
54
|
+
assert.equal(parsed.phase, '10');
|
|
55
|
+
assert.equal(parsed.padded, '10');
|
|
56
|
+
assert.equal(parsed.depth, 'standard');
|
|
57
|
+
assert.ok(parsed.review_path.endsWith('10-REVIEW.md'));
|
|
58
|
+
assert.ok(parsed.agents && parsed.agents.code_reviewer === 'np-code-reviewer');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('CR-2: --depth=deep overrides default', () => {
|
|
62
|
+
const sb = makeSandbox(DEMO_YAML);
|
|
63
|
+
const stdout = makeSink();
|
|
64
|
+
const stderr = makeSink();
|
|
65
|
+
const code = codeReviewCli.run(['10', '--depth=deep'], { cwd: sb, stdout, stderr });
|
|
66
|
+
assert.equal(code, 0);
|
|
67
|
+
const parsed = JSON.parse(stdout.toString());
|
|
68
|
+
assert.equal(parsed.depth, 'deep');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('CR-3: invalid depth rejected with code-review-invalid-depth', () => {
|
|
72
|
+
const sb = makeSandbox(DEMO_YAML);
|
|
73
|
+
const stdout = makeSink();
|
|
74
|
+
const stderr = makeSink();
|
|
75
|
+
const code = codeReviewCli.run(['10', '--depth=sloppy'], { cwd: sb, stdout, stderr });
|
|
76
|
+
assert.equal(code, 1);
|
|
77
|
+
assert.match(stderr.toString(), /"code":\s*"code-review-invalid-depth"/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('CR-4: unknown phase rejected with code-review-not-found', () => {
|
|
81
|
+
const sb = makeSandbox(DEMO_YAML);
|
|
82
|
+
const stdout = makeSink();
|
|
83
|
+
const stderr = makeSink();
|
|
84
|
+
const code = codeReviewCli.run(['999'], { cwd: sb, stdout, stderr });
|
|
85
|
+
assert.equal(code, 1);
|
|
86
|
+
assert.match(stderr.toString(), /"code":\s*"code-review-not-found"/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('CR-5: missing phase arg prints usage', () => {
|
|
90
|
+
const sb = makeSandbox(DEMO_YAML);
|
|
91
|
+
const stdout = makeSink();
|
|
92
|
+
const stderr = makeSink();
|
|
93
|
+
const code = codeReviewCli.run([], { cwd: sb, stdout, stderr });
|
|
94
|
+
assert.equal(code, 1);
|
|
95
|
+
assert.match(stderr.toString(), /Usage:/);
|
|
96
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const { NubosPilotError, findProjectRoot } = require('../../lib/core.cjs');
|
|
5
|
+
const { extractFrontmatter } = require('../../lib/frontmatter.cjs');
|
|
6
|
+
const { TASK_ID_RE, setTaskStatus } = require('../../lib/tasks.cjs');
|
|
7
|
+
const git = require('../../lib/git.cjs');
|
|
8
|
+
const { commitTask, findCommitByTaskId } = git;
|
|
9
|
+
const { deleteCheckpoint } = require('../../lib/checkpoint.cjs');
|
|
10
|
+
|
|
11
|
+
function _resolveTaskFile(taskId, cwd) {
|
|
12
|
+
|
|
13
|
+
const root = findProjectRoot(cwd);
|
|
14
|
+
const phasesRoot = path.join(root, '.nubos-pilot', 'phases');
|
|
15
|
+
let entries;
|
|
16
|
+
try {
|
|
17
|
+
entries = fs.readdirSync(phasesRoot, { withFileTypes: true });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err && err.code === 'ENOENT') {
|
|
20
|
+
throw new NubosPilotError(
|
|
21
|
+
'commit-task-not-found',
|
|
22
|
+
'No .nubos-pilot/phases directory found',
|
|
23
|
+
{ taskId, cwd },
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
const padded = taskId.slice(0, 2);
|
|
29
|
+
for (const e of entries) {
|
|
30
|
+
if (!e.isDirectory()) continue;
|
|
31
|
+
if (!(e.name === padded || e.name.startsWith(padded + '-'))) continue;
|
|
32
|
+
const candidate = path.join(phasesRoot, e.name, 'tasks', taskId + '.md');
|
|
33
|
+
if (fs.existsSync(candidate)) return { filePath: candidate, planDir: path.join(phasesRoot, e.name) };
|
|
34
|
+
}
|
|
35
|
+
throw new NubosPilotError(
|
|
36
|
+
'commit-task-not-found',
|
|
37
|
+
'No task file found for id ' + taskId,
|
|
38
|
+
{ taskId },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function _resolveSafe(root, p) {
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const abs = path.resolve(root, p);
|
|
46
|
+
const rel = path.relative(root, abs);
|
|
47
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
48
|
+
throw new NubosPilotError(
|
|
49
|
+
'path-not-in-project',
|
|
50
|
+
'files_modified entry escapes project root: ' + p,
|
|
51
|
+
{ path: p, root },
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return p;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _extractName(frontmatter, body) {
|
|
58
|
+
if (typeof frontmatter.name === 'string' && frontmatter.name.length > 0) return frontmatter.name;
|
|
59
|
+
|
|
60
|
+
const m = String(body || '').match(/^#\s+(?:Task:\s*)?(.+?)\s*$/m);
|
|
61
|
+
if (m) return m[1].trim();
|
|
62
|
+
return frontmatter.id || 'task';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function run(args, ctx) {
|
|
66
|
+
const context = ctx || {};
|
|
67
|
+
const cwd = context.cwd || process.cwd();
|
|
68
|
+
const stdout = context.stdout || process.stdout;
|
|
69
|
+
const list = Array.isArray(args) ? args : [];
|
|
70
|
+
const taskId = list[0];
|
|
71
|
+
|
|
72
|
+
if (!taskId) {
|
|
73
|
+
throw new NubosPilotError(
|
|
74
|
+
'commit-task-missing-id',
|
|
75
|
+
'commit-task requires a task id (e.g. 06-01-T01)',
|
|
76
|
+
{},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!TASK_ID_RE.test(taskId)) {
|
|
80
|
+
throw new NubosPilotError(
|
|
81
|
+
'commit-task-invalid-id',
|
|
82
|
+
'Invalid task id format: ' + taskId + ' (expected <NN-NN-TNN>)',
|
|
83
|
+
{ taskId },
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { filePath, planDir } = _resolveTaskFile(taskId, cwd);
|
|
88
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
89
|
+
const { frontmatter, body } = extractFrontmatter(raw);
|
|
90
|
+
const files = Array.isArray(frontmatter.files_modified) ? frontmatter.files_modified : [];
|
|
91
|
+
if (files.length === 0) {
|
|
92
|
+
throw new NubosPilotError(
|
|
93
|
+
'commit-task-no-files',
|
|
94
|
+
'Task ' + taskId + ' has empty files_modified',
|
|
95
|
+
{ taskId },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const root = findProjectRoot(cwd);
|
|
99
|
+
const safeFiles = files.map((p) => _resolveSafe(root, p));
|
|
100
|
+
const name = _extractName(frontmatter, body);
|
|
101
|
+
const message = 'task(' + taskId + '): ' + name;
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
commitTask(taskId, safeFiles, message);
|
|
106
|
+
const sha = findCommitByTaskId(taskId);
|
|
107
|
+
|
|
108
|
+
try { deleteCheckpoint(taskId, cwd); } catch { }
|
|
109
|
+
try { setTaskStatus(taskId, 'done', planDir); } catch (err) {
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
process.stderr.write('[nubos-pilot warn] setTaskStatus failed for ' + taskId + ': ' + (err && err.message) + '\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const payload = { ok: true, task_id: taskId, sha, files: safeFiles };
|
|
116
|
+
stdout.write(JSON.stringify(payload));
|
|
117
|
+
return payload;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { run };
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const { test, after } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const { execFileSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const subcmd = require('./commit-task.cjs');
|
|
9
|
+
|
|
10
|
+
const _repos = [];
|
|
11
|
+
|
|
12
|
+
function makeRepo() {
|
|
13
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-ct-'));
|
|
14
|
+
execFileSync('git', ['init', '-q', '-b', 'main', root], { stdio: 'pipe' });
|
|
15
|
+
execFileSync('git', ['-C', root, 'config', 'user.email', 'test@nubos.local']);
|
|
16
|
+
execFileSync('git', ['-C', root, 'config', 'user.name', 'nubos-test']);
|
|
17
|
+
execFileSync('git', ['-C', root, 'commit', '--allow-empty', '-q', '-m', 'chore: init'], { stdio: 'pipe' });
|
|
18
|
+
fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(path.join(root, '.nubos-pilot', 'STATE.md'), `---
|
|
21
|
+
schema_version: 2
|
|
22
|
+
milestone: m1
|
|
23
|
+
milestone_name: m1
|
|
24
|
+
current_phase: null
|
|
25
|
+
current_plan: null
|
|
26
|
+
current_task: null
|
|
27
|
+
last_updated: "2026-04-15T00:00:00Z"
|
|
28
|
+
progress:
|
|
29
|
+
total_phases: 0
|
|
30
|
+
completed_phases: 0
|
|
31
|
+
total_plans: 0
|
|
32
|
+
completed_plans: 0
|
|
33
|
+
percent: 0
|
|
34
|
+
session:
|
|
35
|
+
stopped_at: null
|
|
36
|
+
resume_file: null
|
|
37
|
+
last_activity: null
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# State
|
|
41
|
+
`, 'utf-8');
|
|
42
|
+
_repos.push(root);
|
|
43
|
+
return root;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function seedPlanAndTask(root, planId, taskId, filesModified) {
|
|
47
|
+
const phase = planId.slice(0, 2);
|
|
48
|
+
const phaseDir = path.join(root, '.nubos-pilot', 'phases', phase + '-demo');
|
|
49
|
+
const planDir = phaseDir;
|
|
50
|
+
const tasksDir = path.join(planDir, 'tasks');
|
|
51
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const fm = [
|
|
54
|
+
'---',
|
|
55
|
+
`id: ${taskId}`,
|
|
56
|
+
`phase: ${Number(phase)}`,
|
|
57
|
+
`plan: "${planId}"`,
|
|
58
|
+
'type: auto',
|
|
59
|
+
'status: in-progress',
|
|
60
|
+
'tier: sonnet',
|
|
61
|
+
'owner: np-executor',
|
|
62
|
+
'wave: 1',
|
|
63
|
+
'depends_on: []',
|
|
64
|
+
'files_modified:',
|
|
65
|
+
...filesModified.map((f) => ` - ${f}`),
|
|
66
|
+
'autonomous: true',
|
|
67
|
+
'must_haves:',
|
|
68
|
+
' truths: []',
|
|
69
|
+
'---',
|
|
70
|
+
'',
|
|
71
|
+
'# Task: demo',
|
|
72
|
+
].join('\n');
|
|
73
|
+
fs.writeFileSync(path.join(tasksDir, taskId + '.md'), fm, 'utf-8');
|
|
74
|
+
|
|
75
|
+
fs.writeFileSync(path.join(phaseDir, `${planId}-PLAN.md`), '---\nphase: '+Number(phase)+'\nplan: '+planId.slice(3)+'\n---\n', 'utf-8');
|
|
76
|
+
return { phaseDir, planDir, taskPath: path.join(tasksDir, taskId + '.md') };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _capture() {
|
|
80
|
+
let buf = '';
|
|
81
|
+
const stub = { write: (s) => { buf += s; return true; } };
|
|
82
|
+
return { stub, get: () => buf };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
after(() => {
|
|
86
|
+
while (_repos.length) {
|
|
87
|
+
const r = _repos.pop();
|
|
88
|
+
try { fs.rmSync(r, { recursive: true, force: true }); } catch {}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('CT-1: commit-task requires a task id', () => {
|
|
93
|
+
const root = makeRepo();
|
|
94
|
+
const cap = _capture();
|
|
95
|
+
assert.throws(
|
|
96
|
+
() => subcmd.run([], { cwd: root, stdout: cap.stub }),
|
|
97
|
+
(err) => err && err.code === 'commit-task-missing-id',
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('CT-2: commit-task rejects invalid TASK_ID format (defense-in-depth)', () => {
|
|
102
|
+
const root = makeRepo();
|
|
103
|
+
const cap = _capture();
|
|
104
|
+
assert.throws(
|
|
105
|
+
() => subcmd.run(['bad/id'], { cwd: root, stdout: cap.stub }),
|
|
106
|
+
(err) => err && err.code === 'commit-task-invalid-id',
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('CT-3: commit-task emits JSON with sha + files on success', () => {
|
|
111
|
+
const root = makeRepo();
|
|
112
|
+
seedPlanAndTask(root, '06-01', '06-01-T01', ['src/a.ts']);
|
|
113
|
+
|
|
114
|
+
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
115
|
+
fs.writeFileSync(path.join(root, 'src', 'a.ts'), 'export const a = 1;\n', 'utf-8');
|
|
116
|
+
const prev = process.cwd();
|
|
117
|
+
process.chdir(root);
|
|
118
|
+
const cap = _capture();
|
|
119
|
+
try {
|
|
120
|
+
subcmd.run(['06-01-T01'], { cwd: root, stdout: cap.stub });
|
|
121
|
+
} finally {
|
|
122
|
+
process.chdir(prev);
|
|
123
|
+
}
|
|
124
|
+
const payload = JSON.parse(cap.get());
|
|
125
|
+
assert.equal(payload.ok, true);
|
|
126
|
+
assert.equal(payload.task_id, '06-01-T01');
|
|
127
|
+
assert.ok(/^[0-9a-f]{40}$/.test(payload.sha));
|
|
128
|
+
assert.deepEqual(payload.files, ['src/a.ts']);
|
|
129
|
+
|
|
130
|
+
const subject = execFileSync('git', ['-C', root, 'log', '-n', '1', '--format=%s'], { encoding: 'utf-8' }).trim();
|
|
131
|
+
assert.ok(subject.startsWith('task(06-01-T01):'), 'subject: ' + subject);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('CT-4: commit-task LOUD-FAILS when every files_modified entry is gitignored (D-25)', () => {
|
|
135
|
+
const root = makeRepo();
|
|
136
|
+
seedPlanAndTask(root, '06-01', '06-01-T02', ['build/out.js']);
|
|
137
|
+
fs.writeFileSync(path.join(root, '.gitignore'), 'build/\n', 'utf-8');
|
|
138
|
+
fs.mkdirSync(path.join(root, 'build'), { recursive: true });
|
|
139
|
+
fs.writeFileSync(path.join(root, 'build', 'out.js'), 'noise', 'utf-8');
|
|
140
|
+
const prev = process.cwd();
|
|
141
|
+
process.chdir(root);
|
|
142
|
+
const cap = _capture();
|
|
143
|
+
try {
|
|
144
|
+
assert.throws(
|
|
145
|
+
() => subcmd.run(['06-01-T02'], { cwd: root, stdout: cap.stub }),
|
|
146
|
+
(err) => err && err.code === 'commit-all-paths-gitignored',
|
|
147
|
+
);
|
|
148
|
+
} finally {
|
|
149
|
+
process.chdir(prev);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('CT-5: commit-task unknown task id → task-not-found', () => {
|
|
154
|
+
const root = makeRepo();
|
|
155
|
+
const cap = _capture();
|
|
156
|
+
assert.throws(
|
|
157
|
+
() => subcmd.run(['06-99-T99'], { cwd: root, stdout: cap.stub }),
|
|
158
|
+
(err) => err && err.code === 'commit-task-not-found',
|
|
159
|
+
);
|
|
160
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const { execFileSync } = require('node:child_process');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { NubosPilotError } = require('../../lib/core.cjs');
|
|
4
|
+
const { assertCommittablePaths } = require('../../lib/git.cjs');
|
|
5
|
+
|
|
6
|
+
const MAX_MSG = 2000;
|
|
7
|
+
|
|
8
|
+
function _usage() {
|
|
9
|
+
return 'Usage:\n np-tools.cjs commit "message" --files f1 f2 ...';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function _emitError(err, stderr) {
|
|
13
|
+
const code = err && err.name === 'NubosPilotError' ? err.code : 'commit-internal-error';
|
|
14
|
+
const message = (err && err.message) || String(err);
|
|
15
|
+
const details = (err && err.details) || null;
|
|
16
|
+
stderr.write(JSON.stringify({ code, message, details }) + '\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _parseArgs(argv) {
|
|
20
|
+
const args = Array.isArray(argv) ? argv.slice() : [];
|
|
21
|
+
let msg = null;
|
|
22
|
+
const files = [];
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < args.length) {
|
|
25
|
+
const a = args[i];
|
|
26
|
+
if (a === '--files') {
|
|
27
|
+
i += 1;
|
|
28
|
+
while (i < args.length && !String(args[i]).startsWith('--')) {
|
|
29
|
+
files.push(args[i]);
|
|
30
|
+
i += 1;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (a === '-m' || a === '--message') {
|
|
35
|
+
msg = args[i + 1];
|
|
36
|
+
i += 2;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (msg == null && !String(a).startsWith('--')) {
|
|
40
|
+
msg = a;
|
|
41
|
+
i += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
return { msg, files };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _validateFiles(files) {
|
|
50
|
+
for (const f of files) {
|
|
51
|
+
if (typeof f !== 'string' || f.length === 0) {
|
|
52
|
+
throw new NubosPilotError('commit-invalid-path', 'commit path must be non-empty string', { path: f });
|
|
53
|
+
}
|
|
54
|
+
const segments = String(f).split(/[/\\]/);
|
|
55
|
+
for (const seg of segments) {
|
|
56
|
+
if (seg === '..') {
|
|
57
|
+
throw new NubosPilotError('commit-path-traversal', 'commit path must not contain ".." segments', { path: f });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (path.isAbsolute(f)) {
|
|
61
|
+
throw new NubosPilotError('commit-path-absolute', 'commit path must be relative', { path: f });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function run(argv, ctx) {
|
|
67
|
+
const context = ctx || {};
|
|
68
|
+
const stdout = context.stdout || process.stdout;
|
|
69
|
+
const stderr = context.stderr || process.stderr;
|
|
70
|
+
try {
|
|
71
|
+
const { msg, files } = _parseArgs(argv);
|
|
72
|
+
if (!msg || typeof msg !== 'string' || msg.trim() === '') {
|
|
73
|
+
stderr.write(_usage() + '\n');
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
if (msg.length > MAX_MSG) {
|
|
77
|
+
throw new NubosPilotError('commit-message-too-long', 'commit message exceeds ' + MAX_MSG + ' chars', { length: msg.length });
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
80
|
+
stderr.write(_usage() + '\n');
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
_validateFiles(files);
|
|
84
|
+
const committable = assertCommittablePaths(files);
|
|
85
|
+
if (committable.length === 0) {
|
|
86
|
+
throw new NubosPilotError('commit-no-paths', 'commit invoked with no committable paths', { files });
|
|
87
|
+
}
|
|
88
|
+
execFileSync('git', ['add', '--', ...committable], { stdio: 'pipe' });
|
|
89
|
+
execFileSync('git', ['commit', '-m', msg, '--', ...committable], { stdio: 'pipe' });
|
|
90
|
+
const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf-8' }).trim();
|
|
91
|
+
stdout.write(JSON.stringify({ committed: true, sha, files: committable }) + '\n');
|
|
92
|
+
return 0;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
_emitError(err, stderr);
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { run, _parseArgs, _validateFiles };
|
|
100
|
+
|
|
101
|
+
if (require.main === module) {
|
|
102
|
+
process.exit(run(process.argv.slice(2)));
|
|
103
|
+
}
|