@open-agent-toolkit/cli 0.2.58 → 0.2.62
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/assets/agents/oat-phase-implementer.md +27 -4
- package/assets/agents/oat-reviewer.md +25 -14
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/configuration.md +16 -0
- package/assets/docs/cli-utilities/workflow-gates.md +91 -6
- package/assets/docs/contributing/skills.md +27 -5
- package/assets/docs/provider-sync/commands.md +23 -0
- package/assets/docs/reference/cli-reference.md +10 -3
- package/assets/docs/reference/oat-directory-structure.md +1 -0
- package/assets/docs/workflows/ideas/index.md +1 -1
- package/assets/docs/workflows/index.md +1 -0
- package/assets/docs/workflows/projects/artifacts.md +24 -3
- package/assets/docs/workflows/projects/autonomy.md +5 -2
- package/assets/docs/workflows/projects/dispatch-ceiling.md +43 -7
- package/assets/docs/workflows/projects/hill-checkpoints.md +21 -0
- package/assets/docs/workflows/projects/implementation-execution.md +10 -2
- package/assets/docs/workflows/projects/lifecycle.md +128 -10
- package/assets/docs/workflows/projects/pr-flow.md +2 -0
- package/assets/docs/workflows/projects/reviews.md +1 -1
- package/assets/docs/workflows/projects/state-machine.md +20 -0
- package/assets/docs/workflows/skills/index.md +2 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-brainstorm/SKILL.md +61 -12
- package/assets/skills/oat-docs/SKILL.md +13 -8
- package/assets/skills/oat-doctor/SKILL.md +3 -3
- package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
- package/assets/skills/oat-project-autonomous/SKILL.md +14 -9
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +59 -38
- package/assets/skills/oat-project-capture/SKILL.md +2 -2
- package/assets/skills/oat-project-design/SKILL.md +10 -5
- package/assets/skills/oat-project-discover/SKILL.md +15 -9
- package/assets/skills/oat-project-document/SKILL.md +11 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +59 -38
- package/assets/skills/oat-project-implement/SKILL.md +3 -3
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +149 -24
- package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +14 -8
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +59 -38
- package/assets/skills/oat-project-implement/references/phase-execution.md +16 -1
- package/assets/skills/oat-project-implement/references/plan-and-resume.md +19 -1
- package/assets/skills/oat-project-import-plan/SKILL.md +75 -6
- package/assets/skills/oat-project-lite/SKILL.md +467 -0
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +331 -0
- package/assets/skills/oat-project-next/SKILL.md +54 -11
- package/assets/skills/oat-project-plan/SKILL.md +42 -5
- package/assets/skills/oat-project-plan-writing/SKILL.md +100 -3
- package/assets/skills/oat-project-pr-final/SKILL.md +33 -7
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +59 -38
- package/assets/skills/oat-project-pr-progress/SKILL.md +3 -3
- package/assets/skills/oat-project-progress/SKILL.md +27 -3
- package/assets/skills/oat-project-promote-spec-driven/SKILL.md +3 -1
- package/assets/skills/oat-project-quick-start/SKILL.md +29 -4
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +59 -38
- package/assets/skills/oat-project-review-provide/SKILL.md +37 -11
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +12 -5
- package/assets/skills/oat-project-summary/SKILL.md +27 -3
- package/assets/templates/plan-lite.md +165 -0
- package/assets/templates/plan.md +1 -1
- package/assets/templates/state.md +5 -2
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +102 -8
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +16 -1
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +10 -1
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +2 -0
- package/dist/commands/project/new/index.d.ts +3 -2
- package/dist/commands/project/new/index.d.ts.map +1 -1
- package/dist/commands/project/new/index.js +2 -1
- package/dist/commands/project/new/scaffold.d.ts +5 -3
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +38 -10
- package/dist/commands/project/promote/index.d.ts +2 -0
- package/dist/commands/project/promote/index.d.ts.map +1 -0
- package/dist/commands/project/promote/index.js +1 -0
- package/dist/commands/project/promote/promote.d.ts +47 -0
- package/dist/commands/project/promote/promote.d.ts.map +1 -0
- package/dist/commands/project/promote/promote.js +322 -0
- package/dist/commands/project/split/run.d.ts.map +1 -1
- package/dist/commands/project/split/run.js +8 -1
- package/dist/commands/project/validate-plan/index.d.ts.map +1 -1
- package/dist/commands/project/validate-plan/index.js +25 -1
- package/dist/commands/project/validate-plan/validate-plan.d.ts +8 -0
- package/dist/commands/project/validate-plan/validate-plan.d.ts.map +1 -1
- package/dist/commands/project/validate-plan/validate-plan.js +49 -0
- package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
- package/dist/commands/remove/skill/remove-skill.js +10 -1
- package/dist/commands/shared/frontmatter.d.ts +32 -1
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +93 -0
- package/dist/commands/state/generate.d.ts.map +1 -1
- package/dist/commands/state/generate.js +23 -0
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +8 -1
- package/dist/commands/sync/apply.d.ts.map +1 -1
- package/dist/commands/sync/apply.js +40 -6
- package/dist/commands/sync/index.d.ts.map +1 -1
- package/dist/commands/sync/index.js +11 -17
- package/dist/commands/sync/sync.types.d.ts +6 -6
- package/dist/commands/sync/sync.types.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.js +2 -0
- package/dist/config/resolve.d.ts +16 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +16 -8
- package/dist/engine/execute-plan.d.ts.map +1 -1
- package/dist/engine/execute-plan.js +6 -0
- package/dist/manifest/index.d.ts +2 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +1 -1
- package/dist/manifest/manager.d.ts +40 -0
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js +35 -0
- package/package.json +2 -2
|
@@ -14,6 +14,7 @@ import TOML from '@iarna/toml';
|
|
|
14
14
|
import { getCeilingAdapter, isDirectDispatchRoleName, CLAUDE_TIER_ORDER, } from '../../../providers/ceiling/registry.js';
|
|
15
15
|
import { buildDispatchReport, formatDispatchReport, } from '../../../providers/identity/dispatch-report.js';
|
|
16
16
|
import { classifyModelFamily, } from '../../../providers/identity/family.js';
|
|
17
|
+
import { formatDispatchStamp, } from '../../../providers/identity/stamp.js';
|
|
17
18
|
import { Command, Option } from 'commander';
|
|
18
19
|
import YAML from 'yaml';
|
|
19
20
|
const CODEX_VALUES = [
|
|
@@ -1824,7 +1825,15 @@ async function runDispatchCeilingResolve(context, dependencies, options) {
|
|
|
1824
1825
|
const resolution = await resolveDispatchCeiling(context, dependencies, options);
|
|
1825
1826
|
const dispatchReport = buildResolutionReport(resolution, options);
|
|
1826
1827
|
if (context.json) {
|
|
1827
|
-
|
|
1828
|
+
// The stamp is emitted exactly when report context produced a report, and
|
|
1829
|
+
// always through the canonical formatter: never hand-assembled here.
|
|
1830
|
+
context.logger.json(dispatchReport
|
|
1831
|
+
? {
|
|
1832
|
+
...resolution,
|
|
1833
|
+
dispatchReport,
|
|
1834
|
+
dispatchStamp: formatDispatchStamp(dispatchReport),
|
|
1835
|
+
}
|
|
1836
|
+
: resolution);
|
|
1828
1837
|
}
|
|
1829
1838
|
else {
|
|
1830
1839
|
writeHumanResolution(context, resolution);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,wBAAgB,oBAAoB,IAAI,OAAO,CAwB9C"}
|
|
@@ -11,6 +11,7 @@ import { createProjectMigrateCommand } from './migrate/index.js';
|
|
|
11
11
|
import { createProjectNewCommand } from './new/index.js';
|
|
12
12
|
import { createProjectOpenCommand } from './open/index.js';
|
|
13
13
|
import { createProjectPauseCommand } from './pause/index.js';
|
|
14
|
+
import { createProjectPromoteCommand } from './promote/index.js';
|
|
14
15
|
import { createProjectPruneCommand } from './prune/index.js';
|
|
15
16
|
import { createProjectPullCommand } from './pull/index.js';
|
|
16
17
|
import { createProjectPushCommand } from './push/index.js';
|
|
@@ -34,6 +35,7 @@ export function createProjectCommand() {
|
|
|
34
35
|
.addCommand(createProjectNewCommand())
|
|
35
36
|
.addCommand(createProjectOpenCommand())
|
|
36
37
|
.addCommand(createProjectPauseCommand())
|
|
38
|
+
.addCommand(createProjectPromoteCommand())
|
|
37
39
|
.addCommand(createProjectPruneCommand())
|
|
38
40
|
.addCommand(createProjectPushCommand())
|
|
39
41
|
.addCommand(createProjectPullCommand())
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
2
|
import { type ProjectScope } from '../../shared/project-scope.js';
|
|
3
|
+
import { type WorkflowMode } from '@open-agent-toolkit/control-plane';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
|
-
import { type
|
|
5
|
+
import { type ScaffoldProjectResult } from './scaffold.js';
|
|
5
6
|
interface ProjectNewDependencies {
|
|
6
7
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
7
8
|
scaffoldProject: (options: {
|
|
8
9
|
repoRoot: string;
|
|
9
10
|
projectName: string;
|
|
10
|
-
mode:
|
|
11
|
+
mode: WorkflowMode;
|
|
11
12
|
force: boolean;
|
|
12
13
|
setActive: boolean;
|
|
13
14
|
refreshDashboard: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAE5C,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAC;AAmBpB,UAAU,sBAAsB;IAC9B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,eAAe,EAAE,CAAC,OAAO,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,OAAO,CAAC;QACf,SAAS,EAAE,OAAO,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC;QAChB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,KAAK,CAAC,EAAE,YAAY,CAAC;KACtB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtC;AAmGD,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,CAAC,sBAAsB,CAAM,GAC9C,OAAO,CAgDT"}
|
|
@@ -2,6 +2,7 @@ import { buildCommandContext, } from '../../../app/command-context.js';
|
|
|
2
2
|
import { PROJECT_SCOPES, } from '../../shared/project-scope.js';
|
|
3
3
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
4
4
|
import { CliError } from '../../../errors/cli-error.js';
|
|
5
|
+
import { WORKFLOW_MODES, } from '@open-agent-toolkit/control-plane';
|
|
5
6
|
import { Command, Option } from 'commander';
|
|
6
7
|
import { scaffoldProject as defaultScaffoldProject, } from './scaffold.js';
|
|
7
8
|
const COMMIT_STATUS_MESSAGES = {
|
|
@@ -98,7 +99,7 @@ export function createProjectNewCommand(overrides = {}) {
|
|
|
98
99
|
.description('Create or update an OAT project scaffold')
|
|
99
100
|
.argument('<name>', 'Project name (letters, numbers, dash, underscore)')
|
|
100
101
|
.addOption(new Option('--mode <mode>', 'Scaffold mode')
|
|
101
|
-
.choices([
|
|
102
|
+
.choices([...WORKFLOW_MODES])
|
|
102
103
|
.default('spec-driven'))
|
|
103
104
|
.addOption(new Option('--scope <scope>', 'Project scope').choices(PROJECT_SCOPES))
|
|
104
105
|
.option('--force', 'Allow a duplicate slug in another scope; scaffold remains non-destructive')
|
|
@@ -4,12 +4,12 @@ import { writeSyncedRecord } from '../../project/sync/record.js';
|
|
|
4
4
|
import { commitRecordChange, createSyncedProject, pushSynced, rollbackCreatedSyncedProject } from '../../project/sync/ref-sync.js';
|
|
5
5
|
import { resolveDefaultScope, type ProjectScope } from '../../shared/project-scope.js';
|
|
6
6
|
import { setActiveProject } from '../../../config/oat-config.js';
|
|
7
|
-
|
|
7
|
+
import type { WorkflowMode } from '@open-agent-toolkit/control-plane';
|
|
8
8
|
export interface ScaffoldProjectOptions {
|
|
9
9
|
repoRoot: string;
|
|
10
10
|
projectName: string;
|
|
11
11
|
scope?: ProjectScope;
|
|
12
|
-
mode?:
|
|
12
|
+
mode?: WorkflowMode;
|
|
13
13
|
force?: boolean;
|
|
14
14
|
setActive?: boolean;
|
|
15
15
|
refreshDashboard?: boolean;
|
|
@@ -34,7 +34,7 @@ export interface ScaffoldProjectOptions {
|
|
|
34
34
|
*/
|
|
35
35
|
export type CommitScaffoldStatus = 'committed' | 'skipped_disabled' | 'skipped_no_worktree' | 'skipped_nothing' | 'failed';
|
|
36
36
|
export interface ScaffoldProjectResult {
|
|
37
|
-
mode:
|
|
37
|
+
mode: WorkflowMode;
|
|
38
38
|
scope: ProjectScope;
|
|
39
39
|
projectsRoot: string;
|
|
40
40
|
projectPath: string;
|
|
@@ -60,5 +60,7 @@ export interface ScaffoldProjectDependencies {
|
|
|
60
60
|
ensureScopedRootGitignore: typeof ensureScopedRootGitignore;
|
|
61
61
|
setActiveProject: typeof setActiveProject;
|
|
62
62
|
}
|
|
63
|
+
export declare function applyTemplateReplacements(template: string, projectName: string, today: string, nowUtc: string, mode: WorkflowMode): string;
|
|
64
|
+
export declare function resolveTemplateSource(userOatRoot: string, repoRoot: string, templateFile: string): Promise<string>;
|
|
63
65
|
export declare function scaffoldProject(options: ScaffoldProjectOptions, overrides?: Partial<ScaffoldProjectDependencies>): Promise<ScaffoldProjectResult>;
|
|
64
66
|
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAEL,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,4BAA4B,EAC7B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAGL,mBAAmB,EAGnB,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,15 @@ const TEMPLATES_BY_MODE = {
|
|
|
37
37
|
],
|
|
38
38
|
quick: ['state.md', 'discovery.md', 'plan.md', 'implementation.md'],
|
|
39
39
|
import: ['state.md', 'plan.md', 'implementation.md'],
|
|
40
|
+
lite: [
|
|
41
|
+
'state.md',
|
|
42
|
+
{ source: 'plan-lite.md', target: 'plan.md' },
|
|
43
|
+
'implementation.md',
|
|
44
|
+
],
|
|
40
45
|
};
|
|
46
|
+
function normalizeTemplateEntry(entry) {
|
|
47
|
+
return typeof entry === 'string' ? { source: entry, target: entry } : entry;
|
|
48
|
+
}
|
|
41
49
|
const OAT_PLACEHOLDER_PATTERN = /(?<!\{)\{\s*(OAT_[A-Z0-9_]+)\s*\}(?!\})/g;
|
|
42
50
|
const STATE_TEMPLATE_BY_MODE = {
|
|
43
51
|
'spec-driven': {
|
|
@@ -97,6 +105,22 @@ const STATE_TEMPLATE_BY_MODE = {
|
|
|
97
105
|
],
|
|
98
106
|
nextMilestone: 'Run `oat-project-import-plan` to normalize the external plan',
|
|
99
107
|
},
|
|
108
|
+
lite: {
|
|
109
|
+
hillCheckpoints: '[]',
|
|
110
|
+
phase: 'plan',
|
|
111
|
+
status: 'Plan',
|
|
112
|
+
currentPhase: 'Plan - Authoring a single-phase lite plan',
|
|
113
|
+
artifacts: [
|
|
114
|
+
'- **Plan:** `plan.md` (scaffolded lite template — not started)',
|
|
115
|
+
'- **Implementation:** `implementation.md` (scaffolded template — not started)',
|
|
116
|
+
],
|
|
117
|
+
progress: [
|
|
118
|
+
'- ✓ Lite project scaffolded',
|
|
119
|
+
'- ✓ Plan and implementation artifacts scaffolded',
|
|
120
|
+
'- ⧗ Awaiting critical interview and plan authoring',
|
|
121
|
+
],
|
|
122
|
+
nextMilestone: 'Run `oat-project-lite` to author and approve the plan',
|
|
123
|
+
},
|
|
100
124
|
};
|
|
101
125
|
function replaceOatPlaceholders(template, replacements) {
|
|
102
126
|
return template.replace(OAT_PLACEHOLDER_PATTERN, (placeholder, token) => replacements[token] ?? placeholder);
|
|
@@ -120,7 +144,7 @@ function validateProjectName(name) {
|
|
|
120
144
|
function shellQuote(value) {
|
|
121
145
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
122
146
|
}
|
|
123
|
-
function applyTemplateReplacements(template, projectName, today, nowUtc, mode) {
|
|
147
|
+
export function applyTemplateReplacements(template, projectName, today, nowUtc, mode) {
|
|
124
148
|
const stateContent = STATE_TEMPLATE_BY_MODE[mode];
|
|
125
149
|
const oatReplacements = {
|
|
126
150
|
OAT_HILL_CHECKPOINTS: stateContent.hillCheckpoints,
|
|
@@ -185,21 +209,25 @@ async function commitScaffold(cwd, absoluteProjectPath, projectName, createdFile
|
|
|
185
209
|
async function scaffoldModeTemplates(userOatRoot, repoRoot, absoluteProjectPath, projectName, mode, today, nowUtc) {
|
|
186
210
|
const createdFiles = [];
|
|
187
211
|
const skippedFiles = [];
|
|
188
|
-
for (const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
212
|
+
for (const entry of TEMPLATES_BY_MODE[mode]) {
|
|
213
|
+
const { source, target } = normalizeTemplateEntry(entry);
|
|
214
|
+
const src = await resolveTemplateSource(userOatRoot, repoRoot, source);
|
|
215
|
+
const dest = join(absoluteProjectPath, target);
|
|
191
216
|
if (await fileExists(dest)) {
|
|
192
|
-
skippedFiles.push(
|
|
217
|
+
skippedFiles.push(target);
|
|
193
218
|
continue;
|
|
194
219
|
}
|
|
195
220
|
const template = await readFile(src, 'utf8');
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
221
|
+
let rendered = applyTemplateReplacements(template, projectName, today, nowUtc, mode);
|
|
222
|
+
if (mode === 'lite' && target === 'plan.md') {
|
|
223
|
+
rendered = rendered.replace(/^---\n/, '---\noat_template: true\n');
|
|
224
|
+
}
|
|
225
|
+
assertNoUnresolvedOatPlaceholders(rendered, source);
|
|
226
|
+
if (target === 'state.md') {
|
|
199
227
|
assertValidProjectStateContent(rendered, { filePath: dest });
|
|
200
228
|
}
|
|
201
229
|
await writeFile(dest, rendered, 'utf8');
|
|
202
|
-
createdFiles.push(
|
|
230
|
+
createdFiles.push(target);
|
|
203
231
|
}
|
|
204
232
|
return { createdFiles, skippedFiles };
|
|
205
233
|
}
|
|
@@ -214,7 +242,7 @@ async function scaffoldProjectLog(userOatRoot, repoRoot, absoluteProjectPath, pr
|
|
|
214
242
|
await writeFile(dest, instantiateProjectLogTemplate(template, projectName, today), 'utf8');
|
|
215
243
|
return 'created';
|
|
216
244
|
}
|
|
217
|
-
async function resolveTemplateSource(userOatRoot, repoRoot, templateFile) {
|
|
245
|
+
export async function resolveTemplateSource(userOatRoot, repoRoot, templateFile) {
|
|
218
246
|
const userSource = join(userOatRoot, 'templates', templateFile);
|
|
219
247
|
if (await fileExists(userSource)) {
|
|
220
248
|
return userSource;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/promote/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createProjectPromoteCommand } from './promote.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mkdir as defaultMkdir, readFile as defaultReadFile, rename as defaultRename, writeFile as defaultWriteFile } from 'node:fs/promises';
|
|
2
|
+
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
3
|
+
import { resolveTemplateSource as defaultResolveTemplateSource } from '../../project/new/scaffold.js';
|
|
4
|
+
import { type GitRunner } from '../../project/sync/git.js';
|
|
5
|
+
import { type PushResult, type SyncTarget } from '../../project/sync/ref-sync.js';
|
|
6
|
+
import { fileExists as defaultFileExists } from '../../../fs/io.js';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
export interface LitePlanSections {
|
|
9
|
+
summary: string;
|
|
10
|
+
decisions: string;
|
|
11
|
+
assumptions: string;
|
|
12
|
+
outOfScope: string;
|
|
13
|
+
validationCriteria: string;
|
|
14
|
+
productBehavior?: string;
|
|
15
|
+
technicalDesign?: string;
|
|
16
|
+
}
|
|
17
|
+
type PromotionReason = 'promoted' | 'unsupported-target' | 'not-lite' | 'lite-plan-reference-exists' | 'invalid-lite-plan' | 'scope-unresolved' | 'project-unreadable' | 'template-unreadable' | 'write-failed' | 'persistence-failed';
|
|
18
|
+
export type PromotionResult = {
|
|
19
|
+
status: 'promoted';
|
|
20
|
+
reason: 'promoted';
|
|
21
|
+
files: string[];
|
|
22
|
+
} | {
|
|
23
|
+
status: 'refused';
|
|
24
|
+
reason: Exclude<PromotionReason, 'promoted'>;
|
|
25
|
+
files: string[];
|
|
26
|
+
};
|
|
27
|
+
export interface ProjectPromoteDependencies {
|
|
28
|
+
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
29
|
+
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
30
|
+
resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
|
|
31
|
+
resolveTemplateSource: typeof defaultResolveTemplateSource;
|
|
32
|
+
readFile: typeof defaultReadFile;
|
|
33
|
+
writeFile: typeof defaultWriteFile;
|
|
34
|
+
mkdir: typeof defaultMkdir;
|
|
35
|
+
rename: typeof defaultRename;
|
|
36
|
+
fileExists: typeof defaultFileExists;
|
|
37
|
+
gitRunner: GitRunner;
|
|
38
|
+
pushSynced: (target: SyncTarget, git: GitRunner, options: {
|
|
39
|
+
message?: string;
|
|
40
|
+
}) => Promise<PushResult>;
|
|
41
|
+
processEnv: NodeJS.ProcessEnv;
|
|
42
|
+
now: () => Date;
|
|
43
|
+
}
|
|
44
|
+
export declare function parseLitePlanSections(planContent: string, liteTemplateContent?: string): LitePlanSections | null;
|
|
45
|
+
export declare function createProjectPromoteCommand(overrides?: Partial<ProjectPromoteDependencies>): Command;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=promote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promote.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/promote/promote.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,IAAI,YAAY,EACrB,QAAQ,IAAI,eAAe,EAC3B,MAAM,IAAI,aAAa,EACvB,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,qBAAqB,IAAI,4BAA4B,EACtD,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAEzD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,KAAK,eAAe,GAChB,UAAU,GACV,oBAAoB,GACpB,UAAU,GACV,4BAA4B,GAC5B,mBAAmB,GACnB,kBAAkB,GAClB,oBAAoB,GACpB,qBAAqB,GACrB,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,MAAM,eAAe,GACvB;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAC7C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAMN,MAAM,WAAW,0BAA0B;IACzC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,qBAAqB,EAAE,OAAO,4BAA4B,CAAC;IAC3D,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,KAAK,EAAE,OAAO,YAAY,CAAC;IAC3B,MAAM,EAAE,OAAO,aAAa,CAAC;IAC7B,UAAU,EAAE,OAAO,iBAAiB,CAAC;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,CACV,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,OAAO,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAC1B,OAAO,CAAC,UAAU,CAAC,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AA4ED,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,mBAAmB,CAAC,EAAE,MAAM,GAC3B,gBAAgB,GAAG,IAAI,CAYzB;AA0SD,wBAAgB,2BAA2B,CACzC,SAAS,GAAE,OAAO,CAAC,0BAA0B,CAAM,GAClD,OAAO,CA+BT"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { mkdir as defaultMkdir, readFile as defaultReadFile, rename as defaultRename, writeFile as defaultWriteFile, } from 'node:fs/promises';
|
|
2
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
|
+
import { applyTemplateReplacements, resolveTemplateSource as defaultResolveTemplateSource, } from '../../project/new/scaffold.js';
|
|
5
|
+
import { defaultGitRunner } from '../../project/sync/git.js';
|
|
6
|
+
import { buildSyncTarget, pushSynced as defaultPushSynced, } from '../../project/sync/ref-sync.js';
|
|
7
|
+
import { getFrontmatterBlock } from '../../shared/frontmatter.js';
|
|
8
|
+
import { replaceFrontmatter } from '../../shared/frontmatter-write.js';
|
|
9
|
+
import { resolveProjectsRoot as defaultResolveProjectsRoot } from '../../shared/oat-paths.js';
|
|
10
|
+
import { resolveProjectScope } from '../../shared/project-scope.js';
|
|
11
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
12
|
+
import { fileExists as defaultFileExists } from '../../../fs/io.js';
|
|
13
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import YAML from 'yaml';
|
|
16
|
+
const PROMOTED_FILES = [
|
|
17
|
+
'discovery.md',
|
|
18
|
+
'references/lite-plan.md',
|
|
19
|
+
'plan.md',
|
|
20
|
+
'state.md',
|
|
21
|
+
];
|
|
22
|
+
const LITE_SECTION_HEADINGS = [
|
|
23
|
+
['Summary', 'summary'],
|
|
24
|
+
['Decisions', 'decisions'],
|
|
25
|
+
['Assumptions', 'assumptions'],
|
|
26
|
+
['Out of Scope', 'outOfScope'],
|
|
27
|
+
['Validation Criteria', 'validationCriteria'],
|
|
28
|
+
];
|
|
29
|
+
const DEFAULT_DEPENDENCIES = {
|
|
30
|
+
buildCommandContext,
|
|
31
|
+
resolveProjectRoot,
|
|
32
|
+
resolveProjectsRoot: defaultResolveProjectsRoot,
|
|
33
|
+
resolveTemplateSource: defaultResolveTemplateSource,
|
|
34
|
+
readFile: defaultReadFile,
|
|
35
|
+
writeFile: defaultWriteFile,
|
|
36
|
+
mkdir: defaultMkdir,
|
|
37
|
+
rename: defaultRename,
|
|
38
|
+
fileExists: defaultFileExists,
|
|
39
|
+
gitRunner: defaultGitRunner,
|
|
40
|
+
pushSynced: defaultPushSynced,
|
|
41
|
+
processEnv: process.env,
|
|
42
|
+
now: () => new Date(),
|
|
43
|
+
};
|
|
44
|
+
function extractSection(content, heading) {
|
|
45
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
46
|
+
const headingMatch = new RegExp(`^## ${escapedHeading}[ \\t]*$`, 'm').exec(content);
|
|
47
|
+
if (!headingMatch)
|
|
48
|
+
return null;
|
|
49
|
+
const bodyStart = headingMatch.index + headingMatch[0].length;
|
|
50
|
+
const remaining = content.slice(bodyStart);
|
|
51
|
+
const nextHeading = /^##\s+/m.exec(remaining);
|
|
52
|
+
const bodyEnd = nextHeading ? bodyStart + nextHeading.index : content.length;
|
|
53
|
+
const body = content.slice(bodyStart, bodyEnd).trim();
|
|
54
|
+
return body.length > 0 ? body : null;
|
|
55
|
+
}
|
|
56
|
+
function extractLitePlanSections(planContent) {
|
|
57
|
+
const sections = {};
|
|
58
|
+
for (const [heading, key] of LITE_SECTION_HEADINGS) {
|
|
59
|
+
const body = extractSection(planContent, heading);
|
|
60
|
+
if (!body)
|
|
61
|
+
return null;
|
|
62
|
+
sections[key] = body;
|
|
63
|
+
}
|
|
64
|
+
const productBehavior = extractSection(planContent, 'Product Behavior');
|
|
65
|
+
const technicalDesign = extractSection(planContent, 'Technical Design');
|
|
66
|
+
if (productBehavior)
|
|
67
|
+
sections.productBehavior = productBehavior;
|
|
68
|
+
if (technicalDesign)
|
|
69
|
+
sections.technicalDesign = technicalDesign;
|
|
70
|
+
return sections;
|
|
71
|
+
}
|
|
72
|
+
function containsUnresolvedTemplateContent(sections, templateSections) {
|
|
73
|
+
const containsTemplateMarkers = (key) => {
|
|
74
|
+
const content = sections[key];
|
|
75
|
+
const templateContent = templateSections[key];
|
|
76
|
+
if (!content || !templateContent)
|
|
77
|
+
return false;
|
|
78
|
+
if (content === templateContent)
|
|
79
|
+
return true;
|
|
80
|
+
const markers = templateContent
|
|
81
|
+
.match(/\[[^\]\n]+\]|\{[^{}\n]+\}/g)
|
|
82
|
+
?.filter((marker) => !/^\[\s*\]$/.test(marker)) ?? [];
|
|
83
|
+
const instructionalComments = templateContent.match(/<!--[\s\S]*?-->/g) ?? [];
|
|
84
|
+
return [...markers, ...instructionalComments].some((marker) => content.includes(marker));
|
|
85
|
+
};
|
|
86
|
+
return (LITE_SECTION_HEADINGS.some(([, key]) => containsTemplateMarkers(key)) ||
|
|
87
|
+
['productBehavior', 'technicalDesign'].some((key) => containsTemplateMarkers(key)));
|
|
88
|
+
}
|
|
89
|
+
export function parseLitePlanSections(planContent, liteTemplateContent) {
|
|
90
|
+
const sections = extractLitePlanSections(planContent);
|
|
91
|
+
if (!sections || !liteTemplateContent)
|
|
92
|
+
return sections;
|
|
93
|
+
const templateSections = extractLitePlanSections(liteTemplateContent);
|
|
94
|
+
if (!templateSections ||
|
|
95
|
+
containsUnresolvedTemplateContent(sections, templateSections)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return sections;
|
|
99
|
+
}
|
|
100
|
+
function readObjectFrontmatter(content, filePath) {
|
|
101
|
+
const block = getFrontmatterBlock(content);
|
|
102
|
+
if (!block) {
|
|
103
|
+
throw new Error(`${filePath} is missing frontmatter`);
|
|
104
|
+
}
|
|
105
|
+
const parsed = YAML.parse(block);
|
|
106
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
107
|
+
throw new Error(`${filePath} frontmatter must be a YAML object`);
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
function replaceSection(content, heading, body) {
|
|
112
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
113
|
+
const headingMatch = new RegExp(`^## ${escapedHeading}[ \\t]*$`, 'm').exec(content);
|
|
114
|
+
if (!headingMatch) {
|
|
115
|
+
throw new Error(`discovery template is missing ## ${heading}`);
|
|
116
|
+
}
|
|
117
|
+
const bodyStart = headingMatch.index + headingMatch[0].length;
|
|
118
|
+
const remaining = content.slice(bodyStart);
|
|
119
|
+
const nextHeading = /^##\s+/m.exec(remaining);
|
|
120
|
+
const bodyEnd = nextHeading ? bodyStart + nextHeading.index : content.length;
|
|
121
|
+
const suffix = content.slice(bodyEnd).replace(/^\s*/, '');
|
|
122
|
+
return `${content.slice(0, bodyStart)}\n\n${body.trim()}\n\n${suffix}`;
|
|
123
|
+
}
|
|
124
|
+
function renderDiscovery(template, projectName, today, nowUtc, sections) {
|
|
125
|
+
let rendered = applyTemplateReplacements(template, projectName, today, nowUtc, 'quick');
|
|
126
|
+
const frontmatter = readObjectFrontmatter(rendered, 'rendered discovery.md');
|
|
127
|
+
frontmatter['oat_ready_for'] = 'oat-project-quick-start';
|
|
128
|
+
rendered = replaceFrontmatter(rendered, YAML.stringify(frontmatter).trimEnd());
|
|
129
|
+
const successCriteria = sections.productBehavior
|
|
130
|
+
? `### Product Behavior (from Lite plan)\n\n${sections.productBehavior}\n\n### Validation Criteria (from Lite plan)\n\n${sections.validationCriteria}`
|
|
131
|
+
: sections.validationCriteria;
|
|
132
|
+
for (const [heading, body] of [
|
|
133
|
+
['Initial Request', sections.summary],
|
|
134
|
+
['Key Decisions', sections.decisions],
|
|
135
|
+
['Assumptions', sections.assumptions],
|
|
136
|
+
['Out of Scope', sections.outOfScope],
|
|
137
|
+
['Success Criteria', successCriteria],
|
|
138
|
+
]) {
|
|
139
|
+
rendered = replaceSection(rendered, heading, body);
|
|
140
|
+
}
|
|
141
|
+
if (sections.technicalDesign) {
|
|
142
|
+
const nextSteps = /^## Next Steps[ \t]*$/m.exec(rendered);
|
|
143
|
+
if (!nextSteps) {
|
|
144
|
+
throw new Error('discovery template is missing ## Next Steps');
|
|
145
|
+
}
|
|
146
|
+
const carriedForward = [
|
|
147
|
+
'## Carried-Forward Technical Design',
|
|
148
|
+
'',
|
|
149
|
+
'> Prior implementation context carried forward from the Lite plan; this is not a new discovery deliverable.',
|
|
150
|
+
'',
|
|
151
|
+
sections.technicalDesign,
|
|
152
|
+
'',
|
|
153
|
+
].join('\n');
|
|
154
|
+
rendered = `${rendered.slice(0, nextSteps.index)}${carriedForward}${rendered.slice(nextSteps.index)}`;
|
|
155
|
+
}
|
|
156
|
+
return rendered;
|
|
157
|
+
}
|
|
158
|
+
function renderQuickState(stateContent, statePath, nowUtc) {
|
|
159
|
+
const frontmatter = readObjectFrontmatter(stateContent, statePath);
|
|
160
|
+
frontmatter['oat_workflow_mode'] = 'quick';
|
|
161
|
+
frontmatter['oat_phase'] = 'discovery';
|
|
162
|
+
frontmatter['oat_phase_status'] = 'complete';
|
|
163
|
+
frontmatter['oat_ready_for'] = 'oat-project-quick-start';
|
|
164
|
+
frontmatter['oat_project_state_updated'] = nowUtc;
|
|
165
|
+
return replaceFrontmatter(stateContent, YAML.stringify(frontmatter).trimEnd());
|
|
166
|
+
}
|
|
167
|
+
function repoRelativePath(repoRoot, path) {
|
|
168
|
+
return relative(repoRoot, path).split(sep).join('/');
|
|
169
|
+
}
|
|
170
|
+
async function persistPromotion(repoRoot, projectsRoot, projectRoot, projectName, scope, dependencies) {
|
|
171
|
+
const message = `chore(oat): promote ${projectName} to quick`;
|
|
172
|
+
if (scope === 'synced') {
|
|
173
|
+
const target = buildSyncTarget(repoRoot, projectsRoot, projectName);
|
|
174
|
+
const result = await dependencies.pushSynced(target, dependencies.gitRunner, { message });
|
|
175
|
+
if (result.status !== 'pushed' && result.status !== 'up-to-date') {
|
|
176
|
+
throw new Error(`synced promotion push ${result.status}`);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (scope === 'local') {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const pathspecs = PROMOTED_FILES.map((file) => repoRelativePath(repoRoot, join(projectRoot, file)));
|
|
184
|
+
await dependencies.gitRunner.run(['add', '--', ...pathspecs], {
|
|
185
|
+
cwd: repoRoot,
|
|
186
|
+
});
|
|
187
|
+
await dependencies.gitRunner.run(['commit', '-m', message, '--', ...pathspecs], { cwd: repoRoot });
|
|
188
|
+
}
|
|
189
|
+
async function promoteProject(projectPath, options, context, dependencies) {
|
|
190
|
+
if (options.to !== 'quick') {
|
|
191
|
+
return { status: 'refused', reason: 'unsupported-target', files: [] };
|
|
192
|
+
}
|
|
193
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
194
|
+
const projectRoot = isAbsolute(projectPath)
|
|
195
|
+
? resolve(projectPath)
|
|
196
|
+
: resolve(repoRoot, projectPath);
|
|
197
|
+
const projectName = basename(projectRoot);
|
|
198
|
+
const statePath = join(projectRoot, 'state.md');
|
|
199
|
+
const planPath = join(projectRoot, 'plan.md');
|
|
200
|
+
const referencePath = join(projectRoot, 'references', 'lite-plan.md');
|
|
201
|
+
let stateContent;
|
|
202
|
+
let planContent;
|
|
203
|
+
try {
|
|
204
|
+
[stateContent, planContent] = await Promise.all([
|
|
205
|
+
dependencies.readFile(statePath, 'utf8'),
|
|
206
|
+
dependencies.readFile(planPath, 'utf8'),
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return { status: 'refused', reason: 'project-unreadable', files: [] };
|
|
211
|
+
}
|
|
212
|
+
let stateFrontmatter;
|
|
213
|
+
try {
|
|
214
|
+
stateFrontmatter = readObjectFrontmatter(stateContent, statePath);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return { status: 'refused', reason: 'project-unreadable', files: [] };
|
|
218
|
+
}
|
|
219
|
+
if (stateFrontmatter['oat_workflow_mode'] !== 'lite') {
|
|
220
|
+
return { status: 'refused', reason: 'not-lite', files: [] };
|
|
221
|
+
}
|
|
222
|
+
if (await dependencies.fileExists(referencePath)) {
|
|
223
|
+
return {
|
|
224
|
+
status: 'refused',
|
|
225
|
+
reason: 'lite-plan-reference-exists',
|
|
226
|
+
files: [],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const userOatRoot = join(context.home, '.oat');
|
|
230
|
+
let liteTemplateContent;
|
|
231
|
+
try {
|
|
232
|
+
const liteTemplatePath = await dependencies.resolveTemplateSource(userOatRoot, repoRoot, 'plan-lite.md');
|
|
233
|
+
liteTemplateContent = await dependencies.readFile(liteTemplatePath, 'utf8');
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return { status: 'refused', reason: 'template-unreadable', files: [] };
|
|
237
|
+
}
|
|
238
|
+
const sections = parseLitePlanSections(planContent, liteTemplateContent);
|
|
239
|
+
if (!sections) {
|
|
240
|
+
return { status: 'refused', reason: 'invalid-lite-plan', files: [] };
|
|
241
|
+
}
|
|
242
|
+
const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
|
|
243
|
+
const scope = resolveProjectScope(projectRoot, projectsRoot, repoRoot);
|
|
244
|
+
if (!scope) {
|
|
245
|
+
return { status: 'refused', reason: 'scope-unresolved', files: [] };
|
|
246
|
+
}
|
|
247
|
+
const now = dependencies.now();
|
|
248
|
+
const nowUtc = now.toISOString();
|
|
249
|
+
const today = nowUtc.slice(0, 10);
|
|
250
|
+
let discoveryContent;
|
|
251
|
+
let quickPlanContent;
|
|
252
|
+
let quickStateContent;
|
|
253
|
+
try {
|
|
254
|
+
const [discoveryTemplatePath, quickPlanTemplatePath] = await Promise.all([
|
|
255
|
+
dependencies.resolveTemplateSource(userOatRoot, repoRoot, 'discovery.md'),
|
|
256
|
+
dependencies.resolveTemplateSource(userOatRoot, repoRoot, 'plan.md'),
|
|
257
|
+
]);
|
|
258
|
+
const [discoveryTemplate, quickPlanTemplate] = await Promise.all([
|
|
259
|
+
dependencies.readFile(discoveryTemplatePath, 'utf8'),
|
|
260
|
+
dependencies.readFile(quickPlanTemplatePath, 'utf8'),
|
|
261
|
+
]);
|
|
262
|
+
discoveryContent = renderDiscovery(discoveryTemplate, projectName, today, nowUtc, sections);
|
|
263
|
+
quickPlanContent = applyTemplateReplacements(quickPlanTemplate, projectName, today, nowUtc, 'quick');
|
|
264
|
+
quickStateContent = renderQuickState(stateContent, statePath, nowUtc);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return { status: 'refused', reason: 'template-unreadable', files: [] };
|
|
268
|
+
}
|
|
269
|
+
const writtenFiles = [];
|
|
270
|
+
try {
|
|
271
|
+
await dependencies.mkdir(join(projectRoot, 'references'), {
|
|
272
|
+
recursive: true,
|
|
273
|
+
});
|
|
274
|
+
await dependencies.writeFile(join(projectRoot, 'discovery.md'), discoveryContent, 'utf8');
|
|
275
|
+
writtenFiles.push('discovery.md');
|
|
276
|
+
await dependencies.rename(planPath, referencePath);
|
|
277
|
+
writtenFiles.push('references/lite-plan.md');
|
|
278
|
+
await dependencies.writeFile(planPath, quickPlanContent, 'utf8');
|
|
279
|
+
writtenFiles.push('plan.md');
|
|
280
|
+
await dependencies.writeFile(statePath, quickStateContent, 'utf8');
|
|
281
|
+
writtenFiles.push('state.md');
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return { status: 'refused', reason: 'write-failed', files: writtenFiles };
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await persistPromotion(repoRoot, projectsRoot, projectRoot, projectName, scope, dependencies);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return {
|
|
291
|
+
status: 'refused',
|
|
292
|
+
reason: 'persistence-failed',
|
|
293
|
+
files: writtenFiles,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
status: 'promoted',
|
|
298
|
+
reason: 'promoted',
|
|
299
|
+
files: [...PROMOTED_FILES],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
export function createProjectPromoteCommand(overrides = {}) {
|
|
303
|
+
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
304
|
+
return new Command('promote')
|
|
305
|
+
.description('Promote an authored lite project to the quick workflow')
|
|
306
|
+
.argument('<project-path>', 'Lite project path to promote')
|
|
307
|
+
.requiredOption('--to <mode>', 'Target workflow mode')
|
|
308
|
+
.action(async (projectPath, options, command) => {
|
|
309
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
310
|
+
const result = await promoteProject(projectPath, options, context, dependencies);
|
|
311
|
+
if (context.json) {
|
|
312
|
+
context.logger.json(result);
|
|
313
|
+
}
|
|
314
|
+
else if (result.status === 'promoted') {
|
|
315
|
+
context.logger.info(`Promoted ${projectPath} to quick.`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
context.logger.error(`Promotion refused: ${result.reason}.`);
|
|
319
|
+
}
|
|
320
|
+
process.exitCode = result.status === 'promoted' ? 0 : 1;
|
|
321
|
+
});
|
|
322
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAOlF,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,UAAU,oBAAoB;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,gBAAgB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,OAAO,iBAAiB,CAAC;CACtC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAOlF,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMhE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,UAAU,oBAAoB;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,gBAAgB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,OAAO,iBAAiB,CAAC;CACtC;AAqND,wBAAgB,4BAA4B,CAC1C,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CA2LT"}
|