dotmd-cli 0.68.0 → 0.70.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/README.md +144 -964
- package/bin/dotmd.mjs +241 -197
- package/dotmd.config.example.mjs +5 -8
- package/package.json +6 -10
- package/src/agent-context.mjs +132 -0
- package/src/atomic-mutation.mjs +1505 -0
- package/src/baton.mjs +109 -87
- package/src/bulk-tag.mjs +7 -7
- package/src/commands.mjs +326 -12
- package/src/completions.mjs +38 -98
- package/src/config.mjs +18 -3
- package/src/diff.mjs +7 -3
- package/src/doctor.mjs +12 -5
- package/src/export.mjs +154 -25
- package/src/fix-refs.mjs +2 -0
- package/src/frontmatter-fix.mjs +2 -0
- package/src/frontmatter.mjs +3 -2
- package/src/git.mjs +531 -14
- package/src/graph.mjs +53 -25
- package/src/guard.mjs +163 -60
- package/src/hud.mjs +65 -76
- package/src/index-file.mjs +28 -16
- package/src/index.mjs +17 -12
- package/src/init.mjs +1 -1
- package/src/journal.mjs +145 -12
- package/src/lifecycle.mjs +554 -282
- package/src/lint.mjs +57 -9
- package/src/managed-path.mjs +192 -0
- package/src/migrate-prompts.mjs +2 -0
- package/src/migrate-template.mjs +2 -0
- package/src/migrate.mjs +7 -1
- package/src/new.mjs +135 -54
- package/src/output-identity.mjs +106 -0
- package/src/pickup-card.mjs +24 -10
- package/src/pickup.mjs +457 -0
- package/src/prompts.mjs +138 -32
- package/src/query.mjs +22 -10
- package/src/reference-planner.mjs +292 -0
- package/src/rename.mjs +65 -73
- package/src/render.mjs +17 -8
- package/src/runlist.mjs +109 -71
- package/src/section.mjs +2 -1
- package/src/ship.mjs +39 -20
- package/src/stats.mjs +1 -1
- package/src/status-metadata.mjs +87 -0
- package/src/statuses.mjs +11 -26
- package/src/summary.mjs +14 -3
- package/src/update.mjs +38 -10
- package/src/use.mjs +4 -1
- package/src/util.mjs +1 -0
- package/src/validate.mjs +14 -6
- package/src/watch.mjs +6 -1
- package/src/notion.mjs +0 -528
package/src/commands.mjs
CHANGED
|
@@ -1,13 +1,327 @@
|
|
|
1
|
-
// Canonical
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// Canonical command grammar. Execution stays in bin/dotmd.mjs; this schema owns
|
|
2
|
+
// names, aliases, visibility, options, positional arity, help groups, and policy.
|
|
3
|
+
const none = Object.freeze({ mutation: 'none', pathPolicy: 'read-only' });
|
|
4
|
+
const mutates = (pathPolicy) => Object.freeze({ mutation: 'conditional', pathPolicy });
|
|
5
|
+
|
|
6
|
+
const flag = (...names) => Object.freeze({ names: Object.freeze(names), arity: 0 });
|
|
7
|
+
const value = (...names) => Object.freeze({ names: Object.freeze(names), arity: 1 });
|
|
8
|
+
const optionalValue = (...names) => Object.freeze({ names: Object.freeze(names), arity: 'optional' });
|
|
9
|
+
const positionals = (min = 0, max = min) => Object.freeze({ min, max });
|
|
10
|
+
const form = (syntax, { subcommands = [], args = positionals(), options = [], passthrough = false, dashPositionalsAfter = Infinity } = {}) => Object.freeze({
|
|
11
|
+
syntax,
|
|
12
|
+
subcommands: Object.freeze(subcommands),
|
|
13
|
+
positionals: args,
|
|
14
|
+
options: Object.freeze(options),
|
|
15
|
+
passthrough,
|
|
16
|
+
dashPositionalsAfter,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const GLOBAL_OPTIONS = Object.freeze([
|
|
20
|
+
value('--config'), value('--root'), value('--type'),
|
|
21
|
+
flag('--dry-run', '-n'), flag('--verbose'), flag('--help', '-h'), flag('--version', '-v'),
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const QUERY_OPTIONS = Object.freeze([
|
|
25
|
+
value('--type'), value('--status'), value('--keyword'), flag('--body'), value('--owner'),
|
|
26
|
+
value('--surface'), value('--module'), value('--domain'), value('--audience'),
|
|
27
|
+
value('--execution-mode'), value('--updated-since'), value('--limit'), value('--sort'),
|
|
28
|
+
value('--group'), flag('--all'), flag('--include-archived'), flag('--exclude-archived'),
|
|
29
|
+
flag('--stale'), flag('--has-next-step'), flag('--has-blockers'), flag('--checklist-open'),
|
|
30
|
+
flag('--json'), flag('--git'), flag('--summarize'), value('--summarize-limit'), value('--model'),
|
|
31
|
+
]);
|
|
32
|
+
const LIFECYCLE_OPTIONS = Object.freeze([
|
|
33
|
+
value('--note'), flag('--no-index'), flag('--show-files'), flag('--force'),
|
|
34
|
+
]);
|
|
35
|
+
const STATUS_PROPERTY_OPTIONS = Object.freeze([
|
|
36
|
+
value('--type'), value('--like'), flag('--yes', '-y'), flag('--ignore-lifecycle-override'), flag('--json'),
|
|
37
|
+
value('--context'), value('--staleDays'),
|
|
38
|
+
flag('--requiresModule', '--no-requiresModule'), flag('--terminal', '--no-terminal'),
|
|
39
|
+
flag('--archive', '--no-archive'), flag('--skipStale', '--no-skipStale'),
|
|
40
|
+
flag('--skipWarnings', '--no-skipWarnings'), flag('--quiet', '--no-quiet'),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const command = (name, policy, group, forms, extra = {}) => Object.freeze({
|
|
44
|
+
name,
|
|
45
|
+
policy,
|
|
46
|
+
group,
|
|
47
|
+
forms: Object.freeze(forms),
|
|
48
|
+
aliases: Object.freeze(extra.aliases ?? []),
|
|
49
|
+
visibility: extra.visibility ?? 'public',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const definitions = [
|
|
53
|
+
command('help', none, 'setup', [form('[topic]', { args: positionals(0, 1) })]),
|
|
54
|
+
command('completions', none, 'setup', [form('<bash|zsh>', { args: positionals(1, 1) })]),
|
|
55
|
+
command('init', mutates('repository setup paths and config files'), 'setup', [form('', { options: [flag('--force')] })]),
|
|
56
|
+
command('watch', mutates('proxy: child command policy applies'), 'setup', [form('[command...]', { args: positionals(0, Infinity), passthrough: true })]),
|
|
57
|
+
|
|
58
|
+
command('list', none, 'read', [form('', { options: [flag('--json'), flag('--verbose')] })]),
|
|
59
|
+
command('json', none, 'read', [form('')]),
|
|
60
|
+
command('coverage', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
61
|
+
command('stats', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
62
|
+
command('graph', none, 'read', [form('', { options: [flag('--dot'), flag('--json'), value('--status'), value('--module'), value('--surface')] })]),
|
|
63
|
+
command('deps', none, 'read', [form('[file]', { args: positionals(0, 1), options: [flag('--json'), value('--depth')] })]),
|
|
64
|
+
command('briefing', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
65
|
+
command('context', none, 'read', [form('', { options: [flag('--json'), flag('--compact'), flag('--summarize'), value('--model')] })]),
|
|
66
|
+
command('agent-context', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
67
|
+
command('hud', none, 'read', [form('', { options: [flag('--json'), flag('--subagent')] })]),
|
|
68
|
+
command('focus', none, 'read', [form('[status]', { args: positionals(0, 1), options: [flag('--json')] })]),
|
|
69
|
+
command('query', none, 'read', [form('[terms...]', { args: positionals(0, Infinity), options: QUERY_OPTIONS })]),
|
|
70
|
+
command('grep', none, 'read', [form('<term> [terms...]', { args: positionals(1, Infinity), options: QUERY_OPTIONS })]),
|
|
71
|
+
command('plans', none, 'read', [
|
|
72
|
+
form('[terms...]', { args: positionals(0, Infinity), options: QUERY_OPTIONS }),
|
|
73
|
+
form('status [terms...]', { subcommands: ['status'], args: positionals(0, Infinity), options: QUERY_OPTIONS }),
|
|
74
|
+
]),
|
|
75
|
+
command('runlists', none, 'read', [form('', { options: [flag('--json'), value('--limit'), value('--sort')] })]),
|
|
76
|
+
command('roadmaps', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
77
|
+
command('diff', none, 'read', [form('[file]', { args: positionals(0, 1), options: [flag('--stat'), value('--since'), flag('--summarize'), value('--model')] })]),
|
|
78
|
+
command('summary', none, 'read', [form('<file>', { args: positionals(1, 1), options: [value('--model'), value('--max-tokens'), flag('--json')] })]),
|
|
79
|
+
command('unblocks', none, 'read', [form('<file>', { args: positionals(1, 1), options: [flag('--json')] })]),
|
|
80
|
+
command('health', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
81
|
+
command('glossary', none, 'read', [form('[term]', { args: positionals(0, 1), options: [flag('--list'), flag('--json')] })]),
|
|
82
|
+
command('modules', none, 'read', [form('', { options: [value('--sort'), value('--limit'), flag('--all'), flag('--json')] })]),
|
|
83
|
+
command('module', none, 'read', [form('<name>', { args: positionals(1, 1), options: [value('--sort'), flag('--json')] })]),
|
|
84
|
+
command('surfaces', none, 'read', [form('', { options: [flag('--json')] })]),
|
|
85
|
+
command('journal', none, 'read', [form('', { options: [value('--tail'), flag('--errors'), value('--session'), value('--since'), flag('--by-command'), flag('--json')] })]),
|
|
86
|
+
command('misuse', none, 'read', [form('', { options: [flag('--json'), value('--tail'), flag('--by-rule'), value('--repo')] })]),
|
|
87
|
+
|
|
88
|
+
command('roadmap', mutates('managed source when `next`; otherwise read-only'), 'workflow', [
|
|
89
|
+
form('[hub]', { args: positionals(0, 1), options: [flag('--json')] }),
|
|
90
|
+
form('next [hub]', { subcommands: ['next'], args: positionals(0, 1), options: [flag('--json'), flag('--full'), flag('--no-index')] }),
|
|
91
|
+
]),
|
|
92
|
+
command('prompts', mutates('managed sources/destinations by subcommand'), 'workflow', [
|
|
93
|
+
form('', { options: [flag('--json'), value('--status'), flag('--include-archived'), value('--sort'), value('--limit'), flag('--all')] }),
|
|
94
|
+
form('list', { subcommands: ['list', 'status'], options: [flag('--json'), value('--status'), flag('--include-archived'), value('--sort'), value('--limit'), flag('--all')] }),
|
|
95
|
+
form('next', { subcommands: ['next'] }),
|
|
96
|
+
form('use [file]', { subcommands: ['use', 'resume'], args: positionals(0, 1), options: [flag('--no-index'), flag('--show-files'), flag('--force')] }),
|
|
97
|
+
form('show [file]', { subcommands: ['show', 'peek'], args: positionals(0, 1), options: [flag('--json')] }),
|
|
98
|
+
form('archive <file>', { subcommands: ['archive'], args: positionals(1, 1), options: [flag('--no-index'), flag('--show-files')] }),
|
|
99
|
+
form('new <slug> [body...]', { subcommands: ['new'], args: positionals(1, Infinity), options: [value('--body', '--message'), value('--title'), value('--status')] }),
|
|
100
|
+
form('hold <file>', { subcommands: ['hold', 'shelve'], args: positionals(1, 1) }),
|
|
101
|
+
form('unhold <file>', { subcommands: ['unhold', 'unshelve'], args: positionals(1, 1) }),
|
|
102
|
+
], { aliases: ['prompt'] }),
|
|
103
|
+
command('use', mutates('managed source when starting/consuming; docs remain read-only'), 'workflow', [form('[file]', { args: positionals(0, 1), options: [flag('--json'), flag('--full'), flag('--no-index'), flag('--show-files'), flag('--force')] })]),
|
|
104
|
+
command('next', mutates('managed prompt source and same-root archive destination'), 'workflow', [form('', { options: [flag('--json'), flag('--no-index'), flag('--show-files'), flag('--force')] })]),
|
|
105
|
+
command('baton', mutates('managed plan/prompt sources and managed prompt destination'), 'workflow', [form('[plan|slug] <@draft|->', { args: positionals(0, 2), options: [value('--status'), value('--note'), value('--body', '--message'), flag('--force'), flag('--json')] })]),
|
|
106
|
+
command('runlist', mutates('managed hubs/children and managed scaffold destinations'), 'workflow', [
|
|
107
|
+
form('<hub>', { args: positionals(1, 1), options: [flag('--json')] }),
|
|
108
|
+
form('next <hub>', { subcommands: ['next'], args: positionals(1, 1), options: [flag('--json'), flag('--full'), flag('--no-index'), flag('--show-files')] }),
|
|
109
|
+
form('add <hub> <child...>', { subcommands: ['add'], args: positionals(2, Infinity), options: [flag('--json')] }),
|
|
110
|
+
form('remove <hub> <child...>', { subcommands: ['remove'], args: positionals(2, Infinity), options: [flag('--json'), flag('--clear-parent')] }),
|
|
111
|
+
form('reorder <hub> <child...>', { subcommands: ['reorder'], args: positionals(2, Infinity), options: [flag('--json'), value('--before'), value('--after')] }),
|
|
112
|
+
]),
|
|
113
|
+
|
|
114
|
+
command('export', mutates('external output path intentionally unrestricted'), 'mutate', [form('[file]', { args: positionals(0, 1), options: [value('--format'), value('--output'), value('--status'), value('--module'), value('--root'), value('--type')] })]),
|
|
115
|
+
command('guard', mutates('global misuse log; no managed document writes'), 'mutate', [form('')]),
|
|
116
|
+
command('update', mutates('global CLI and plugin installation state'), 'mutate', [form('', { options: [flag('--check'), flag('--cli-only'), flag('--plugin-only')] })]),
|
|
117
|
+
command('status', mutates('managed source and same-root destination'), 'mutate', [form('<file> [status]', { args: positionals(1, 2), options: LIFECYCLE_OPTIONS })]),
|
|
118
|
+
command('set', mutates('managed source and same-root destination'), 'mutate', [form('<status> [file]', { args: positionals(1, 2), options: LIFECYCLE_OPTIONS })]),
|
|
119
|
+
command('ship', mutates('repository release/index paths and global release tooling'), 'mutate', [form('[patch|minor|major]', { args: positionals(0, 1) })]),
|
|
120
|
+
command('archive', mutates('managed source and same-root destination'), 'mutate', [form('<file>', { args: positionals(1, 1), options: [...LIFECYCLE_OPTIONS, flag('--closeout-template')] })]),
|
|
121
|
+
command('bulk', mutates('managed source sweep; archive destinations preserve roots'), 'mutate', [
|
|
122
|
+
form('archive <files...>', { subcommands: ['archive'], args: positionals(1, Infinity), options: [flag('--json'), flag('--no-index'), flag('--show-files')] }),
|
|
123
|
+
form('tag [files...]', { subcommands: ['tag'], args: positionals(0, Infinity), options: [value('--type'), value('--status'), flag('--json')] }),
|
|
124
|
+
]),
|
|
125
|
+
command('bulk-tag', mutates('managed source sweep'), 'mutate', [form('[files...]', { args: positionals(0, Infinity), options: [value('--type'), value('--status'), flag('--json')] })]),
|
|
126
|
+
command('touch', mutates('managed source or managed source sweep'), 'mutate', [form('[file]', { args: positionals(0, 1), options: [flag('--git')] })]),
|
|
127
|
+
command('new', mutates('managed document destination; external body input unrestricted'), 'mutate', [form('[type] <name> [body...]', {
|
|
128
|
+
args: positionals(0, Infinity),
|
|
129
|
+
options: [value('--status'), value('--title'), value('--runlist'), flag('--coordination'), flag('--roadmap'), flag('--lite', '--minimal'), flag('--audit', '--findings'), value('--body', '--message'), value('--root'), flag('--show-files'), flag('--list-templates', '--list-types')],
|
|
130
|
+
dashPositionalsAfter: 1,
|
|
131
|
+
})]),
|
|
132
|
+
command('lint', mutates('managed source sweep with --fix; otherwise read-only'), 'mutate', [form('', { options: [flag('--fix')] })]),
|
|
133
|
+
command('rename', mutates('managed source, same-root destination, and rewrite sweep'), 'mutate', [form('<old> [new]', { args: positionals(1, 2), options: [flag('--show-files')] })]),
|
|
134
|
+
command('migrate', mutates('managed source sweep'), 'mutate', [form('<field> <old> <new> [files...]', { args: positionals(3, Infinity), options: [flag('--show-files')] })]),
|
|
135
|
+
command('fix-refs', mutates('managed source sweep'), 'mutate', [form('', { options: [flag('--show-files')] })]),
|
|
136
|
+
command('doctor', mutates('managed sweeps, repo index, and maintenance config paths by mode'), 'mutate', [form('[path]', { args: positionals(0, 1), options: [flag('--apply', '--yes'), flag('--statuses'), optionalValue('--migrate-template'), flag('--migrate-prompts'), flag('--frontmatter-fix'), flag('--project'), flag('--json'), flag('--include-archived')] })]),
|
|
137
|
+
command('statuses', mutates('project config path; document scan is read-only'), 'mutate', [
|
|
138
|
+
form('list', { subcommands: ['list'], options: [value('--type'), flag('--json')] }),
|
|
139
|
+
form('add <name>', { subcommands: ['add'], args: positionals(1, 1), options: STATUS_PROPERTY_OPTIONS }),
|
|
140
|
+
form('set <name>', { subcommands: ['set'], args: positionals(1, 1), options: STATUS_PROPERTY_OPTIONS }),
|
|
141
|
+
form('remove <name>', { subcommands: ['remove'], args: positionals(1, 1), options: STATUS_PROPERTY_OPTIONS }),
|
|
142
|
+
form('migrate <type>', { subcommands: ['migrate'], args: positionals(1, 1), options: [flag('--yes', '-y'), flag('--json'), flag('--ignore-lifecycle-override')] }),
|
|
143
|
+
form('', { options: [value('--type'), flag('--json')] }),
|
|
144
|
+
]),
|
|
145
|
+
command('check', mutates('managed fix sweeps and repo-generated index; otherwise validation'), 'mutate', [form('[paths...]', { args: positionals(0, Infinity), options: [flag('--fix'), flag('--errors-only'), flag('--no-collapse'), flag('--json'), flag('--verbose')] })]),
|
|
146
|
+
command('index', mutates('repo-generated index destination; --print is read-only'), 'mutate', [form('', { options: [flag('--print')] })]),
|
|
147
|
+
|
|
148
|
+
command('self-check', none, 'internal', [form('', { options: [flag('--json')] })], { visibility: 'internal' }),
|
|
149
|
+
command('pickup', none, 'removed', [form('[args...]', { args: positionals(0, Infinity) })], { visibility: 'removed' }),
|
|
150
|
+
command('unpickup', none, 'removed', [form('[args...]', { args: positionals(0, Infinity) })], { visibility: 'removed' }),
|
|
151
|
+
command('release', none, 'removed', [form('[args...]', { args: positionals(0, Infinity) })], { visibility: 'removed' }),
|
|
152
|
+
command('finish', none, 'removed', [form('[args...]', { args: positionals(0, Infinity) })], { visibility: 'removed' }),
|
|
153
|
+
command('handoff', none, 'removed', [form('[args...]', { args: positionals(0, Infinity) })], { visibility: 'removed' }),
|
|
154
|
+
|
|
155
|
+
command('stale', none, 'preset', [form('[terms...]', { args: positionals(0, Infinity), options: QUERY_OPTIONS })]),
|
|
156
|
+
command('actionable', none, 'preset', [form('[terms...]', { args: positionals(0, Infinity), options: QUERY_OPTIONS })]),
|
|
13
157
|
];
|
|
158
|
+
|
|
159
|
+
function schemaErrors(schema) {
|
|
160
|
+
const errors = [];
|
|
161
|
+
const names = new Set();
|
|
162
|
+
const aliases = new Set();
|
|
163
|
+
for (const entry of schema) {
|
|
164
|
+
if (!entry.name || names.has(entry.name)) errors.push(`duplicate or empty command name: ${entry.name}`);
|
|
165
|
+
names.add(entry.name);
|
|
166
|
+
if (!entry.group) errors.push(`${entry.name}: missing help group`);
|
|
167
|
+
if (!entry.forms.length) errors.push(`${entry.name}: missing forms`);
|
|
168
|
+
for (const alias of entry.aliases) {
|
|
169
|
+
if (!alias || names.has(alias) || aliases.has(alias)) errors.push(`${entry.name}: duplicate alias ${alias}`);
|
|
170
|
+
aliases.add(alias);
|
|
171
|
+
}
|
|
172
|
+
const formKeys = new Set();
|
|
173
|
+
for (const commandForm of entry.forms) {
|
|
174
|
+
const key = commandForm.subcommands.join('|') || '<default>';
|
|
175
|
+
if (formKeys.has(key)) errors.push(`${entry.name}: duplicate form ${key}`);
|
|
176
|
+
formKeys.add(key);
|
|
177
|
+
const { min, max } = commandForm.positionals;
|
|
178
|
+
if (!Number.isInteger(min) || min < 0 || !(max === Infinity || Number.isInteger(max)) || max < min) {
|
|
179
|
+
errors.push(`${entry.name} ${key}: malformed positional arity`);
|
|
180
|
+
}
|
|
181
|
+
const optionNames = new Set();
|
|
182
|
+
for (const option of commandForm.options) {
|
|
183
|
+
if (![0, 1, 'optional'].includes(option.arity) || !option.names.length) errors.push(`${entry.name} ${key}: malformed option`);
|
|
184
|
+
for (const optionName of option.names) {
|
|
185
|
+
if (!optionName.startsWith('-') || optionNames.has(optionName)) errors.push(`${entry.name} ${key}: duplicate or malformed option ${optionName}`);
|
|
186
|
+
optionNames.add(optionName);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const alias of aliases) if (names.has(alias)) errors.push(`alias collides with command: ${alias}`);
|
|
192
|
+
return errors;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const errors = schemaErrors(definitions);
|
|
196
|
+
if (errors.length) throw new Error(`Invalid command schema:\n${errors.join('\n')}`);
|
|
197
|
+
|
|
198
|
+
export const COMMAND_SCHEMA = Object.freeze(Object.fromEntries(definitions.map(entry => [entry.name, entry])));
|
|
199
|
+
const ALIASES = Object.freeze(Object.fromEntries(definitions.flatMap(entry => entry.aliases.map(alias => [alias, entry.name]))));
|
|
200
|
+
|
|
201
|
+
export const COMMAND_POLICIES = Object.freeze(Object.fromEntries(definitions.map(entry => [entry.name, entry.policy])));
|
|
202
|
+
export const KNOWN_COMMANDS = Object.freeze(definitions.map(entry => entry.name));
|
|
203
|
+
export const PUBLIC_COMMANDS = Object.freeze(definitions.filter(entry => entry.visibility === 'public').map(entry => entry.name));
|
|
204
|
+
export const COMPLETION_COMMANDS = Object.freeze(definitions
|
|
205
|
+
.filter(entry => entry.visibility === 'public')
|
|
206
|
+
.flatMap(entry => [entry.name, ...entry.aliases]));
|
|
207
|
+
export const MUTATION_CAPABLE_COMMANDS = Object.freeze(new Set(
|
|
208
|
+
definitions.filter(entry => entry.policy.mutation !== 'none').map(entry => entry.name),
|
|
209
|
+
));
|
|
210
|
+
|
|
211
|
+
export function canonicalCommand(name) {
|
|
212
|
+
return ALIASES[name] ?? name;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function commandDefinition(name) {
|
|
216
|
+
return COMMAND_SCHEMA[canonicalCommand(name)] ?? null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function commandPolicy(name) {
|
|
220
|
+
return commandDefinition(name)?.policy ?? null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function commandOwnsOption(name, optionName) {
|
|
224
|
+
const definition = commandDefinition(name);
|
|
225
|
+
return Boolean(definition?.forms.some(commandForm => commandForm.options.some(option => option.names.includes(optionName))));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function commandCompletionWords(name) {
|
|
229
|
+
const definition = commandDefinition(name);
|
|
230
|
+
if (!definition) return [];
|
|
231
|
+
const words = new Set();
|
|
232
|
+
for (const commandForm of definition.forms) {
|
|
233
|
+
for (const subcommand of commandForm.subcommands) words.add(subcommand);
|
|
234
|
+
for (const option of commandForm.options) for (const optionName of option.names) words.add(optionName);
|
|
235
|
+
}
|
|
236
|
+
return [...words];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function commandUsage(name) {
|
|
240
|
+
const definition = commandDefinition(name);
|
|
241
|
+
if (!definition) return null;
|
|
242
|
+
return definition.forms.map(commandForm => `dotmd ${definition.name}${commandForm.syntax ? ` ${commandForm.syntax}` : ''}`).join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function optionMap(forms) {
|
|
246
|
+
const map = new Map();
|
|
247
|
+
for (const commandForm of forms) {
|
|
248
|
+
for (const option of commandForm.options) {
|
|
249
|
+
for (const optionName of option.names) map.set(optionName, option);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return map;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function scanArgs(name, argv, options, passthrough = false, dashPositionalsAfter = Infinity) {
|
|
256
|
+
const positional = [];
|
|
257
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
258
|
+
const arg = argv[i];
|
|
259
|
+
if (arg === '-') { positional.push(arg); continue; }
|
|
260
|
+
if (!arg.startsWith('-')) { positional.push(arg); continue; }
|
|
261
|
+
const option = options.get(arg);
|
|
262
|
+
if (!option) {
|
|
263
|
+
if (passthrough) continue;
|
|
264
|
+
if (positional.length >= dashPositionalsAfter) { positional.push(arg); continue; }
|
|
265
|
+
throw new Error(`Unknown flag for \`dotmd ${name}\`: ${arg}`);
|
|
266
|
+
}
|
|
267
|
+
if (option.arity === 1) {
|
|
268
|
+
const next = argv[i + 1];
|
|
269
|
+
if (next === undefined || options.has(next)) {
|
|
270
|
+
throw new Error(`Missing value for \`${arg}\` in \`dotmd ${name}\`.`);
|
|
271
|
+
}
|
|
272
|
+
i += 1;
|
|
273
|
+
} else if (option.arity === 'optional' && argv[i + 1] !== undefined && !argv[i + 1].startsWith('-')) {
|
|
274
|
+
i += 1;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return positional;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function normalizeCommandArgs(name, argv) {
|
|
281
|
+
const canonical = canonicalCommand(name);
|
|
282
|
+
if (canonical !== 'roadmap') return [...argv];
|
|
283
|
+
const positionalIndexes = [];
|
|
284
|
+
const options = optionMap(commandDefinition(canonical).forms);
|
|
285
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
286
|
+
const option = options.get(argv[i]);
|
|
287
|
+
if (option) {
|
|
288
|
+
if (option.arity === 1) i += 1;
|
|
289
|
+
else if (option.arity === 'optional' && argv[i + 1] !== undefined && !argv[i + 1].startsWith('-')) i += 1;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!argv[i].startsWith('-')) positionalIndexes.push(i);
|
|
293
|
+
}
|
|
294
|
+
if (positionalIndexes.length === 2 && argv[positionalIndexes[1]] === 'next' && argv[positionalIndexes[0]] !== 'next') {
|
|
295
|
+
const normalized = [...argv];
|
|
296
|
+
const hub = normalized[positionalIndexes[0]];
|
|
297
|
+
normalized[positionalIndexes[0]] = 'next';
|
|
298
|
+
normalized[positionalIndexes[1]] = hub;
|
|
299
|
+
return normalized;
|
|
300
|
+
}
|
|
301
|
+
return [...argv];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function validateCommandArgs(name, argv, { preset = false } = {}) {
|
|
305
|
+
const definition = preset ? COMMAND_SCHEMA.query : commandDefinition(name);
|
|
306
|
+
if (!definition) return argv;
|
|
307
|
+
const canonical = definition.name;
|
|
308
|
+
const normalized = normalizeCommandArgs(canonical, argv);
|
|
309
|
+
const allOptions = optionMap(definition.forms);
|
|
310
|
+
const allPassthrough = definition.forms.some(commandForm => commandForm.passthrough);
|
|
311
|
+
const dashPositionalsAfter = Math.min(...definition.forms.map(commandForm => commandForm.dashPositionalsAfter));
|
|
312
|
+
const preliminary = scanArgs(canonical, normalized, allOptions, allPassthrough, dashPositionalsAfter);
|
|
313
|
+
const selected = definition.forms.find(commandForm => commandForm.subcommands.includes(preliminary[0]))
|
|
314
|
+
?? definition.forms.find(commandForm => commandForm.subcommands.length === 0);
|
|
315
|
+
if (!selected) throw new Error(`Unknown subcommand for \`dotmd ${canonical}\`: ${preliminary[0] ?? '(missing)'}`);
|
|
316
|
+
const selectedOptions = optionMap([selected]);
|
|
317
|
+
const positional = scanArgs(canonical, normalized, selectedOptions, selected.passthrough, selected.dashPositionalsAfter);
|
|
318
|
+
const args = selected.subcommands.includes(positional[0]) ? positional.slice(1) : positional;
|
|
319
|
+
if (args.length < selected.positionals.min || args.length > selected.positionals.max) {
|
|
320
|
+
throw new Error(`Usage: ${commandUsage(canonical)}`);
|
|
321
|
+
}
|
|
322
|
+
return normalized;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function validateCommandSchema(schema = definitions) {
|
|
326
|
+
return schemaErrors(schema);
|
|
327
|
+
}
|
package/src/completions.mjs
CHANGED
|
@@ -1,107 +1,41 @@
|
|
|
1
1
|
import { die } from './util.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { COMPLETION_COMMANDS, GLOBAL_OPTIONS, PUBLIC_COMMANDS, commandCompletionWords } from './commands.mjs';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const COMMANDS = KNOWN_COMMANDS.filter(c => !COMPLETION_DENYLIST.has(c));
|
|
9
|
-
|
|
10
|
-
const GLOBAL_FLAGS = ['--config', '--dry-run', '--verbose', '--root', '--type', '--help', '--version'];
|
|
11
|
-
|
|
12
|
-
// Shared filter flags for the query-style commands (mirrors QUERY_FLAGS in
|
|
13
|
-
// bin/dotmd.mjs). Kept as one array so query/grep/stale/actionable/plans stay
|
|
14
|
-
// in lockstep.
|
|
15
|
-
const QUERY_FLAGS = [
|
|
16
|
-
'--type', '--status', '--keyword', '--body', '--owner', '--surface', '--module',
|
|
17
|
-
'--domain', '--audience', '--execution-mode', '--updated-since', '--limit', '--sort',
|
|
18
|
-
'--group', '--all', '--include-archived', '--exclude-archived', '--stale',
|
|
19
|
-
'--has-next-step', '--has-blockers', '--checklist-open', '--json', '--git',
|
|
20
|
-
'--summarize', '--summarize-limit', '--model',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const COMMAND_FLAGS = {
|
|
24
|
-
query: QUERY_FLAGS,
|
|
25
|
-
grep: QUERY_FLAGS,
|
|
26
|
-
stale: QUERY_FLAGS,
|
|
27
|
-
actionable: QUERY_FLAGS,
|
|
28
|
-
plans: ['status', ...QUERY_FLAGS],
|
|
29
|
-
list: ['--verbose', '--json'],
|
|
30
|
-
briefing: ['--json'],
|
|
31
|
-
context: ['--json', '--compact', '--summarize', '--model'],
|
|
32
|
-
'agent-context': ['--json'],
|
|
33
|
-
hud: ['--json', '--subagent'],
|
|
34
|
-
index: ['--write'],
|
|
35
|
-
coverage: ['--json'],
|
|
36
|
-
stats: ['--json'],
|
|
37
|
-
graph: ['--dot', '--json', '--status', '--module', '--surface'],
|
|
38
|
-
deps: ['--json', '--depth'],
|
|
39
|
-
unblocks: ['--json'],
|
|
40
|
-
health: ['--json'],
|
|
41
|
-
glossary: ['--list', '--json'],
|
|
42
|
-
modules: ['--sort', '--json'],
|
|
43
|
-
module: ['--json'],
|
|
44
|
-
surfaces: ['--json'],
|
|
45
|
-
runlists: ['--json', '--limit', '--sort'],
|
|
46
|
-
runlist: ['next', '--json', '--full', '--no-index', '--show-files'],
|
|
47
|
-
prompts: ['list', 'next', 'use', 'show', 'archive', 'new', 'hold', 'unhold',
|
|
48
|
-
'--json', '--status', '--include-archived', '--sort', '--limit', '--all'],
|
|
49
|
-
use: [],
|
|
50
|
-
next: [],
|
|
51
|
-
baton: ['--status', '--note', '--body', '--message', '--dry-run'],
|
|
52
|
-
set: [],
|
|
53
|
-
status: [],
|
|
54
|
-
archive: ['--note', '--no-index', '--show-files', '--closeout-template'],
|
|
55
|
-
bulk: ['archive', 'tag'],
|
|
56
|
-
statuses: ['list', 'add', '--type', '--like', '--json'],
|
|
57
|
-
update: ['--check', '--cli-only', '--plugin-only'],
|
|
58
|
-
misuse: ['--json', '--tail', '--by-rule', '--repo'],
|
|
59
|
-
journal: ['--tail', '--errors', '--session', '--since', '--by-command', '--json'],
|
|
60
|
-
new: ['--status', '--title', '--template', '--list-templates', '--root', '--message', '--body'],
|
|
61
|
-
notion: ['import', 'export', 'sync', '--force', '--dry-run'],
|
|
62
|
-
export: ['--format', '--output', '--status', '--module', '--root', '--type'],
|
|
63
|
-
focus: ['--json'],
|
|
64
|
-
summary: ['--model', '--max-tokens', '--json'],
|
|
65
|
-
diff: ['--stat', '--since', '--summarize', '--model'],
|
|
66
|
-
touch: ['--git'],
|
|
67
|
-
check: ['--fix', '--errors-only', '--no-collapse', '--json', '--verbose'],
|
|
68
|
-
doctor: ['--apply', '--yes', '--dry-run', '--statuses', '--migrate-template',
|
|
69
|
-
'--migrate-prompts', '--frontmatter-fix', '--project', '--json', '--include-archived'],
|
|
70
|
-
lint: ['--fix'],
|
|
71
|
-
ship: [],
|
|
72
|
-
rename: [],
|
|
73
|
-
migrate: [],
|
|
74
|
-
'fix-refs': [],
|
|
75
|
-
};
|
|
4
|
+
const GLOBAL_FLAGS = GLOBAL_OPTIONS.flatMap(option => option.names);
|
|
5
|
+
const COMMAND_WORDS = Object.freeze(Object.fromEntries(
|
|
6
|
+
PUBLIC_COMMANDS.map(command => [command, commandCompletionWords(command)]),
|
|
7
|
+
));
|
|
76
8
|
|
|
77
9
|
function bashCompletion() {
|
|
78
10
|
return `# dotmd bash completion
|
|
79
11
|
# Add to ~/.bashrc: eval "$(dotmd completions bash)"
|
|
80
12
|
_dotmd() {
|
|
81
|
-
local cur
|
|
13
|
+
local cur cmd expect_value
|
|
82
14
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
83
|
-
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
84
|
-
|
|
85
|
-
# Find the subcommand
|
|
86
15
|
cmd=""
|
|
16
|
+
expect_value=0
|
|
17
|
+
|
|
18
|
+
# Find the command while skipping pre-command global flags and their values.
|
|
87
19
|
for ((i=1; i < COMP_CWORD; i++)); do
|
|
20
|
+
if (( expect_value )); then expect_value=0; continue; fi
|
|
88
21
|
case "\${COMP_WORDS[i]}" in
|
|
22
|
+
--config|--root|--type) expect_value=1 ;;
|
|
23
|
+
--dry-run|-n|--verbose|--help|-h|--version|-v) ;;
|
|
89
24
|
-*) ;;
|
|
90
25
|
*) cmd="\${COMP_WORDS[i]}"; break ;;
|
|
91
26
|
esac
|
|
92
27
|
done
|
|
93
28
|
|
|
94
|
-
# Complete commands if no subcommand yet
|
|
95
29
|
if [[ -z "$cmd" ]]; then
|
|
96
|
-
COMPREPLY=( $(compgen -W "${
|
|
30
|
+
COMPREPLY=( $(compgen -W "${COMPLETION_COMMANDS.join(' ')} ${GLOBAL_FLAGS.join(' ')}" -- "$cur") )
|
|
97
31
|
return
|
|
98
32
|
fi
|
|
99
33
|
|
|
100
|
-
# Per-command flag completion
|
|
101
34
|
case "$cmd" in
|
|
102
|
-
${Object.entries(
|
|
103
|
-
` ${
|
|
35
|
+
${Object.entries(COMMAND_WORDS).map(([command, words]) =>
|
|
36
|
+
` ${command}) COMPREPLY=( $(compgen -W "${words.join(' ')} ${GLOBAL_FLAGS.join(' ')}" -- "$cur") ) ;;`
|
|
104
37
|
).join('\n')}
|
|
38
|
+
prompt) COMPREPLY=( $(compgen -W "${commandCompletionWords('prompts').join(' ')} ${GLOBAL_FLAGS.join(' ')}" -- "$cur") ) ;;
|
|
105
39
|
*) COMPREPLY=( $(compgen -W "${GLOBAL_FLAGS.join(' ')}" -- "$cur") ) ;;
|
|
106
40
|
esac
|
|
107
41
|
}
|
|
@@ -114,23 +48,35 @@ function zshCompletion() {
|
|
|
114
48
|
_dotmd() {
|
|
115
49
|
local -a commands global_flags
|
|
116
50
|
commands=(
|
|
117
|
-
${
|
|
51
|
+
${COMPLETION_COMMANDS.map(command => ` '${command}'`).join('\n')}
|
|
118
52
|
)
|
|
119
53
|
global_flags=(
|
|
120
|
-
${GLOBAL_FLAGS.map(
|
|
54
|
+
${GLOBAL_FLAGS.map(option => ` '${option}'`).join('\n')}
|
|
121
55
|
)
|
|
122
56
|
|
|
123
|
-
|
|
57
|
+
local cmd=''
|
|
58
|
+
local expect_value=0
|
|
59
|
+
for ((i=2; i < CURRENT; i++)); do
|
|
60
|
+
if (( expect_value )); then expect_value=0; continue; fi
|
|
61
|
+
case "\${words[i]}" in
|
|
62
|
+
--config|--root|--type) expect_value=1 ;;
|
|
63
|
+
--dry-run|-n|--verbose|--help|-h|--version|-v) ;;
|
|
64
|
+
-*) ;;
|
|
65
|
+
*) cmd="\${words[i]}"; break ;;
|
|
66
|
+
esac
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
if [[ -z "$cmd" ]]; then
|
|
124
70
|
_describe 'command' commands
|
|
125
71
|
_describe 'flag' global_flags
|
|
126
72
|
return
|
|
127
73
|
fi
|
|
128
74
|
|
|
129
|
-
local cmd=\${words[2]}
|
|
130
75
|
case "$cmd" in
|
|
131
|
-
${Object.entries(
|
|
132
|
-
` ${
|
|
76
|
+
${Object.entries(COMMAND_WORDS).map(([command, words]) =>
|
|
77
|
+
` ${command}) _values 'arguments' ${words.map(word => `'${word}'`).join(' ')} ;;`
|
|
133
78
|
).join('\n')}
|
|
79
|
+
prompt) _values 'arguments' ${commandCompletionWords('prompts').map(word => `'${word}'`).join(' ')} ;;
|
|
134
80
|
esac
|
|
135
81
|
|
|
136
82
|
_describe 'flag' global_flags
|
|
@@ -140,14 +86,8 @@ compdef _dotmd dotmd`;
|
|
|
140
86
|
|
|
141
87
|
export function runCompletions(argv) {
|
|
142
88
|
const shell = argv[0];
|
|
143
|
-
if (!shell)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
process.stdout.write(bashCompletion() + '\n');
|
|
148
|
-
} else if (shell === 'zsh') {
|
|
149
|
-
process.stdout.write(zshCompletion() + '\n');
|
|
150
|
-
} else {
|
|
151
|
-
die(`Unsupported shell: ${shell}\nSupported: bash, zsh`);
|
|
152
|
-
}
|
|
89
|
+
if (!shell) die('Usage: dotmd completions <bash|zsh>');
|
|
90
|
+
if (shell === 'bash') process.stdout.write(bashCompletion() + '\n');
|
|
91
|
+
else if (shell === 'zsh') process.stdout.write(zshCompletion() + '\n');
|
|
92
|
+
else die(`Unsupported shell: ${shell}\nSupported: bash, zsh`);
|
|
153
93
|
}
|
package/src/config.mjs
CHANGED
|
@@ -55,6 +55,7 @@ const DEFAULTS = {
|
|
|
55
55
|
},
|
|
56
56
|
|
|
57
57
|
lifecycle: {
|
|
58
|
+
startableStatuses: ['active', 'planned'],
|
|
58
59
|
archiveStatuses: ['archived'],
|
|
59
60
|
skipStaleFor: ['archived', 'reference', 'partial', 'queued-after'],
|
|
60
61
|
skipWarningsFor: ['archived', 'partial', 'queued-after'],
|
|
@@ -112,8 +113,6 @@ const DEFAULTS = {
|
|
|
112
113
|
|
|
113
114
|
glossary: null,
|
|
114
115
|
|
|
115
|
-
notion: null,
|
|
116
|
-
|
|
117
116
|
// Opt-in JSONL command journal at .dotmd/journal.jsonl. Default off — agents
|
|
118
117
|
// and users who want usage observability flip this on (or set DOTMD_JOURNAL=1).
|
|
119
118
|
journal: false,
|
|
@@ -141,6 +140,7 @@ const VALID_CONTEXT_VALUES = new Set(['expanded', 'listed', 'counted']);
|
|
|
141
140
|
*/
|
|
142
141
|
function normalizeRichStatuses(config, userConfig) {
|
|
143
142
|
const derived = {
|
|
143
|
+
startableStatuses: [],
|
|
144
144
|
archiveStatuses: [],
|
|
145
145
|
skipStaleFor: [],
|
|
146
146
|
skipWarningsFor: [],
|
|
@@ -199,6 +199,7 @@ function normalizeRichStatuses(config, userConfig) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (p.archive && !derived.archiveStatuses.includes(name)) derived.archiveStatuses.push(name);
|
|
202
|
+
if (p.startable && !derived.startableStatuses.includes(name)) derived.startableStatuses.push(name);
|
|
202
203
|
if ((p.skipStale || quietImpliesSkipStale) && !derived.skipStaleFor.includes(name)) derived.skipStaleFor.push(name);
|
|
203
204
|
if ((p.skipWarnings || quietImpliesSkipWarnings) && !derived.skipWarningsFor.includes(name)) derived.skipWarningsFor.push(name);
|
|
204
205
|
if (p.terminal && !derived.terminalStatuses.includes(name)) derived.terminalStatuses.push(name);
|
|
@@ -241,6 +242,9 @@ function applyDerivedConfig(config, userConfig, derived) {
|
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
// lifecycle — each sub-key independently
|
|
245
|
+
if (!userConfig.lifecycle?.startableStatuses && derived.startableStatuses.length) {
|
|
246
|
+
config.lifecycle.startableStatuses = derived.startableStatuses;
|
|
247
|
+
}
|
|
244
248
|
if (!userConfig.lifecycle?.archiveStatuses && derived.archiveStatuses.length) {
|
|
245
249
|
config.lifecycle.archiveStatuses = derived.archiveStatuses;
|
|
246
250
|
}
|
|
@@ -298,6 +302,15 @@ function validateConfig(userConfig, config, validStatuses, indexPath) {
|
|
|
298
302
|
warnings.push('Config: archiveDir must be a string.');
|
|
299
303
|
}
|
|
300
304
|
|
|
305
|
+
// Lifecycle status lists must reference configured statuses.
|
|
306
|
+
if (config.lifecycle?.startableStatuses) {
|
|
307
|
+
for (const s of config.lifecycle.startableStatuses) {
|
|
308
|
+
if (!validStatuses.has(s)) {
|
|
309
|
+
warnings.push(`Config: lifecycle.startableStatuses contains unknown status '${s}'.`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
301
314
|
// lifecycle.archiveStatuses values must exist in validStatuses
|
|
302
315
|
if (config.lifecycle?.archiveStatuses) {
|
|
303
316
|
for (const s of config.lifecycle.archiveStatuses) {
|
|
@@ -486,6 +499,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
486
499
|
// Lifecycle config
|
|
487
500
|
const lifecycle = config.lifecycle;
|
|
488
501
|
const archiveStatuses = new Set(lifecycle.archiveStatuses);
|
|
502
|
+
const startableStatuses = new Set(lifecycle.startableStatuses ?? ['active', 'planned']);
|
|
489
503
|
const skipStaleFor = new Set(lifecycle.skipStaleFor);
|
|
490
504
|
const skipWarningsFor = new Set(lifecycle.skipWarningsFor);
|
|
491
505
|
const terminalStatuses = new Set(lifecycle.terminalStatuses);
|
|
@@ -526,7 +540,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
526
540
|
rootValidStatuses,
|
|
527
541
|
staleDaysByStatus,
|
|
528
542
|
|
|
529
|
-
lifecycle: { archiveStatuses, skipStaleFor, skipWarningsFor, terminalStatuses, filedStatuses, archiveNestedTypes },
|
|
543
|
+
lifecycle: { archiveStatuses, startableStatuses, skipStaleFor, skipWarningsFor, terminalStatuses, filedStatuses, archiveNestedTypes },
|
|
530
544
|
|
|
531
545
|
validSurfaces,
|
|
532
546
|
validModules,
|
|
@@ -542,6 +556,7 @@ export async function resolveConfig(cwd, explicitConfigPath) {
|
|
|
542
556
|
display: config.display,
|
|
543
557
|
referenceFields: config.referenceFields,
|
|
544
558
|
presets: config.presets,
|
|
559
|
+
configuredPresetNames: new Set(Object.keys(userConfig.presets ?? {})),
|
|
545
560
|
journal: config.journal === true,
|
|
546
561
|
guard: { deny: config.guard?.deny !== false },
|
|
547
562
|
hooks,
|
package/src/diff.mjs
CHANGED
|
@@ -75,16 +75,21 @@ function printFileDiff(relPath, since, diffOutput, opts) {
|
|
|
75
75
|
process.stdout.write(bold(relPath) + dim(` (updated: ${since})`) + '\n');
|
|
76
76
|
|
|
77
77
|
if (opts.summarize) {
|
|
78
|
+
const previewSkipped = Boolean(opts.config?._execution?.suppressSideEffects);
|
|
78
79
|
let summary;
|
|
79
80
|
try {
|
|
80
|
-
summary =
|
|
81
|
+
summary = previewSkipped
|
|
82
|
+
? null
|
|
83
|
+
: opts.config?.hooks?.summarizeDiff
|
|
81
84
|
? opts.config.hooks.summarizeDiff(diffOutput, relPath)
|
|
82
85
|
: summarizeDiffText(diffOutput, relPath, opts.model);
|
|
83
86
|
} catch (err) {
|
|
84
87
|
warn(`Hook 'summarizeDiff' threw: ${err.message}`);
|
|
85
88
|
summary = null;
|
|
86
89
|
}
|
|
87
|
-
if (
|
|
90
|
+
if (previewSkipped) {
|
|
91
|
+
process.stdout.write(dim(' [preview] Summary generation skipped; models and custom summarizeDiff hooks are not invoked.') + '\n');
|
|
92
|
+
} else if (summary) {
|
|
88
93
|
process.stdout.write(dim(` Summary: ${summary}`) + '\n');
|
|
89
94
|
} else {
|
|
90
95
|
warn(' Summary unavailable (model call failed)');
|
|
@@ -94,4 +99,3 @@ function printFileDiff(relPath, since, diffOutput, opts) {
|
|
|
94
99
|
process.stdout.write(diffOutput);
|
|
95
100
|
process.stdout.write('\n');
|
|
96
101
|
}
|
|
97
|
-
|