orizu 0.5.6 → 0.5.7
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/dist/manifests-cli.js +78 -2
- package/package.json +1 -1
- package/src/manifests-cli.ts +83 -2
package/dist/manifests-cli.js
CHANGED
|
@@ -73,6 +73,66 @@ function asRecord(value) {
|
|
|
73
73
|
function num(value) {
|
|
74
74
|
return typeof value === 'number' ? value : 0;
|
|
75
75
|
}
|
|
76
|
+
// -- list table (ALI-1038) ----------------------------------------------------
|
|
77
|
+
/** "3m" / "4h" / "2d" — enough resolution to pick a manifest out of a list. */
|
|
78
|
+
function relativeAge(iso, now) {
|
|
79
|
+
if (typeof iso !== 'string')
|
|
80
|
+
return '-';
|
|
81
|
+
const then = Date.parse(iso);
|
|
82
|
+
if (Number.isNaN(then))
|
|
83
|
+
return '-';
|
|
84
|
+
const mins = Math.max(0, Math.floor((now - then) / 60_000));
|
|
85
|
+
if (mins < 60)
|
|
86
|
+
return `${mins}m`;
|
|
87
|
+
const hours = Math.floor(mins / 60);
|
|
88
|
+
if (hours < 48)
|
|
89
|
+
return `${hours}h`;
|
|
90
|
+
return `${Math.floor(hours / 24)}d`;
|
|
91
|
+
}
|
|
92
|
+
/** One-glance change summary from the stored proposedState — no live API call. */
|
|
93
|
+
function changesSummary(manifest) {
|
|
94
|
+
if (manifest.actionType !== 'repo_merge')
|
|
95
|
+
return '-';
|
|
96
|
+
const proposed = asRecord(manifest.proposedState);
|
|
97
|
+
const files = num(proposed.filesChanged);
|
|
98
|
+
const parts = `${files} file${files === 1 ? '' : 's'} +${num(proposed.additions)}/-${num(proposed.deletions)}`;
|
|
99
|
+
// Instruction files steer agent behavior — reviewers should see the flag in the list.
|
|
100
|
+
return proposed.touches_instruction_files === true ? `${parts} !instr` : parts;
|
|
101
|
+
}
|
|
102
|
+
/** Reviewable-first (pending_approval, then draft), newest first within a group. */
|
|
103
|
+
function listOrder(a, b) {
|
|
104
|
+
const rank = (m) => m.status === 'pending_approval' ? 0 : m.status === 'draft' ? 1 : 2;
|
|
105
|
+
const byRank = rank(a) - rank(b);
|
|
106
|
+
if (byRank !== 0)
|
|
107
|
+
return byRank;
|
|
108
|
+
const created = (m) => typeof m.createdAt === 'string' ? Date.parse(m.createdAt) || 0 : 0;
|
|
109
|
+
return created(b) - created(a);
|
|
110
|
+
}
|
|
111
|
+
/** Render manifests as the standard padEnd table (matches teams/projects list). */
|
|
112
|
+
function manifestsTable(manifests, now) {
|
|
113
|
+
if (manifests.length === 0)
|
|
114
|
+
return 'promotion manifests\n(none)';
|
|
115
|
+
const rows = [...manifests].sort(listOrder).map(manifest => ({
|
|
116
|
+
id: typeof manifest.id === 'string' ? manifest.id : '(unknown)',
|
|
117
|
+
status: typeof manifest.status === 'string' ? manifest.status : '(unknown)',
|
|
118
|
+
type: typeof manifest.actionType === 'string' ? manifest.actionType : '(unknown)',
|
|
119
|
+
changes: changesSummary(manifest),
|
|
120
|
+
author: typeof manifest.authorActorType === 'string' ? manifest.authorActorType : '-',
|
|
121
|
+
age: relativeAge(manifest.createdAt, now),
|
|
122
|
+
}));
|
|
123
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length));
|
|
124
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length));
|
|
125
|
+
const typeWidth = Math.max('TYPE'.length, ...rows.map(row => row.type.length));
|
|
126
|
+
const changesWidth = Math.max('CHANGES'.length, ...rows.map(row => row.changes.length));
|
|
127
|
+
const authorWidth = Math.max('AUTHOR'.length, ...rows.map(row => row.author.length));
|
|
128
|
+
const header = `${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TYPE'.padEnd(typeWidth)} ` +
|
|
129
|
+
`${'CHANGES'.padEnd(changesWidth)} ${'AUTHOR'.padEnd(authorWidth)} AGE`;
|
|
130
|
+
const divider = `${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(typeWidth)} ` +
|
|
131
|
+
`${'-'.repeat(changesWidth)} ${'-'.repeat(authorWidth)} ---`;
|
|
132
|
+
const body = rows.map(row => `${row.id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.type.padEnd(typeWidth)} ` +
|
|
133
|
+
`${row.changes.padEnd(changesWidth)} ${row.author.padEnd(authorWidth)} ${row.age}`);
|
|
134
|
+
return [header, divider, ...body].join('\n');
|
|
135
|
+
}
|
|
76
136
|
// repo_merge manifests (ALI-972) render the compare summary from stored evidence
|
|
77
137
|
// — no live GitHub call — so a reviewer can approve informed without a PR.
|
|
78
138
|
function repoMergeShow(manifest) {
|
|
@@ -108,7 +168,7 @@ export async function manifestsCommand(args, io) {
|
|
|
108
168
|
const response = await fetcherFrom(io)(`/api/cli/promotion-manifests${query}`);
|
|
109
169
|
const data = await requireOk(response, 'Manifest list');
|
|
110
170
|
const manifests = Array.isArray(data.manifests) ? data.manifests : [];
|
|
111
|
-
emit(io, { manifests },
|
|
171
|
+
emit(io, { manifests }, manifestsTable(manifests, Date.now()));
|
|
112
172
|
return 0;
|
|
113
173
|
}
|
|
114
174
|
if (subcommand === 'show') {
|
|
@@ -135,7 +195,23 @@ export async function manifestsCommand(args, io) {
|
|
|
135
195
|
});
|
|
136
196
|
const data = await requireOk(response, `Manifest ${subcommand}`);
|
|
137
197
|
const manifest = (data.manifest ?? {});
|
|
138
|
-
|
|
198
|
+
const lines = [manifestLine(manifest)];
|
|
199
|
+
// Reject reaps the session branch best-effort; a failed reap must never be
|
|
200
|
+
// silent (ALI-1043) — surface it with the exact cleanup command.
|
|
201
|
+
if (subcommand === 'reject' && manifest.actionType === 'repo_merge') {
|
|
202
|
+
const outcome = asRecord(manifest.outcome);
|
|
203
|
+
const branch = asRecord(manifest.proposedState).branch;
|
|
204
|
+
if (outcome.branchDeleted === true) {
|
|
205
|
+
lines.push('session branch deleted');
|
|
206
|
+
}
|
|
207
|
+
else if (typeof branch === 'string' && branch.length > 0) {
|
|
208
|
+
lines.push(`warning: the session branch could not be deleted — remove it with: git push origin --delete ${branch}`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
lines.push('warning: the session branch could not be deleted (branch name unavailable)');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
emit(io, { manifest }, lines.join('\n'));
|
|
139
215
|
return 0;
|
|
140
216
|
}
|
|
141
217
|
io.print('Usage: orizu manifests <list|show|approve|reject|apply> ... [--json]');
|
package/package.json
CHANGED
package/src/manifests-cli.ts
CHANGED
|
@@ -93,6 +93,71 @@ function num(value: unknown): number {
|
|
|
93
93
|
return typeof value === 'number' ? value : 0
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// -- list table (ALI-1038) ----------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/** "3m" / "4h" / "2d" — enough resolution to pick a manifest out of a list. */
|
|
99
|
+
function relativeAge(iso: unknown, now: number): string {
|
|
100
|
+
if (typeof iso !== 'string') return '-'
|
|
101
|
+
const then = Date.parse(iso)
|
|
102
|
+
if (Number.isNaN(then)) return '-'
|
|
103
|
+
const mins = Math.max(0, Math.floor((now - then) / 60_000))
|
|
104
|
+
if (mins < 60) return `${mins}m`
|
|
105
|
+
const hours = Math.floor(mins / 60)
|
|
106
|
+
if (hours < 48) return `${hours}h`
|
|
107
|
+
return `${Math.floor(hours / 24)}d`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** One-glance change summary from the stored proposedState — no live API call. */
|
|
111
|
+
function changesSummary(manifest: Record<string, unknown>): string {
|
|
112
|
+
if (manifest.actionType !== 'repo_merge') return '-'
|
|
113
|
+
const proposed = asRecord(manifest.proposedState)
|
|
114
|
+
const files = num(proposed.filesChanged)
|
|
115
|
+
const parts = `${files} file${files === 1 ? '' : 's'} +${num(proposed.additions)}/-${num(proposed.deletions)}`
|
|
116
|
+
// Instruction files steer agent behavior — reviewers should see the flag in the list.
|
|
117
|
+
return proposed.touches_instruction_files === true ? `${parts} !instr` : parts
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Reviewable-first (pending_approval, then draft), newest first within a group. */
|
|
121
|
+
function listOrder(a: Record<string, unknown>, b: Record<string, unknown>): number {
|
|
122
|
+
const rank = (m: Record<string, unknown>): number =>
|
|
123
|
+
m.status === 'pending_approval' ? 0 : m.status === 'draft' ? 1 : 2
|
|
124
|
+
const byRank = rank(a) - rank(b)
|
|
125
|
+
if (byRank !== 0) return byRank
|
|
126
|
+
const created = (m: Record<string, unknown>): number =>
|
|
127
|
+
typeof m.createdAt === 'string' ? Date.parse(m.createdAt) || 0 : 0
|
|
128
|
+
return created(b) - created(a)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Render manifests as the standard padEnd table (matches teams/projects list). */
|
|
132
|
+
function manifestsTable(manifests: Record<string, unknown>[], now: number): string {
|
|
133
|
+
if (manifests.length === 0) return 'promotion manifests\n(none)'
|
|
134
|
+
const rows = [...manifests].sort(listOrder).map(manifest => ({
|
|
135
|
+
id: typeof manifest.id === 'string' ? manifest.id : '(unknown)',
|
|
136
|
+
status: typeof manifest.status === 'string' ? manifest.status : '(unknown)',
|
|
137
|
+
type: typeof manifest.actionType === 'string' ? manifest.actionType : '(unknown)',
|
|
138
|
+
changes: changesSummary(manifest),
|
|
139
|
+
author: typeof manifest.authorActorType === 'string' ? manifest.authorActorType : '-',
|
|
140
|
+
age: relativeAge(manifest.createdAt, now),
|
|
141
|
+
}))
|
|
142
|
+
const idWidth = Math.max('ID'.length, ...rows.map(row => row.id.length))
|
|
143
|
+
const statusWidth = Math.max('STATUS'.length, ...rows.map(row => row.status.length))
|
|
144
|
+
const typeWidth = Math.max('TYPE'.length, ...rows.map(row => row.type.length))
|
|
145
|
+
const changesWidth = Math.max('CHANGES'.length, ...rows.map(row => row.changes.length))
|
|
146
|
+
const authorWidth = Math.max('AUTHOR'.length, ...rows.map(row => row.author.length))
|
|
147
|
+
const header =
|
|
148
|
+
`${'ID'.padEnd(idWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TYPE'.padEnd(typeWidth)} ` +
|
|
149
|
+
`${'CHANGES'.padEnd(changesWidth)} ${'AUTHOR'.padEnd(authorWidth)} AGE`
|
|
150
|
+
const divider =
|
|
151
|
+
`${'-'.repeat(idWidth)} ${'-'.repeat(statusWidth)} ${'-'.repeat(typeWidth)} ` +
|
|
152
|
+
`${'-'.repeat(changesWidth)} ${'-'.repeat(authorWidth)} ---`
|
|
153
|
+
const body = rows.map(
|
|
154
|
+
row =>
|
|
155
|
+
`${row.id.padEnd(idWidth)} ${row.status.padEnd(statusWidth)} ${row.type.padEnd(typeWidth)} ` +
|
|
156
|
+
`${row.changes.padEnd(changesWidth)} ${row.author.padEnd(authorWidth)} ${row.age}`
|
|
157
|
+
)
|
|
158
|
+
return [header, divider, ...body].join('\n')
|
|
159
|
+
}
|
|
160
|
+
|
|
96
161
|
// repo_merge manifests (ALI-972) render the compare summary from stored evidence
|
|
97
162
|
// — no live GitHub call — so a reviewer can approve informed without a PR.
|
|
98
163
|
function repoMergeShow(manifest: Record<string, unknown>): string {
|
|
@@ -130,7 +195,7 @@ export async function manifestsCommand(args: string[], io: ManifestsCommandIo):
|
|
|
130
195
|
const response = await fetcherFrom(io)(`/api/cli/promotion-manifests${query}`)
|
|
131
196
|
const data = await requireOk(response, 'Manifest list')
|
|
132
197
|
const manifests = Array.isArray(data.manifests) ? (data.manifests as Record<string, unknown>[]) : []
|
|
133
|
-
emit(io, { manifests },
|
|
198
|
+
emit(io, { manifests }, manifestsTable(manifests, Date.now()))
|
|
134
199
|
return 0
|
|
135
200
|
}
|
|
136
201
|
|
|
@@ -159,7 +224,23 @@ export async function manifestsCommand(args: string[], io: ManifestsCommandIo):
|
|
|
159
224
|
})
|
|
160
225
|
const data = await requireOk(response, `Manifest ${subcommand}`)
|
|
161
226
|
const manifest = (data.manifest ?? {}) as Record<string, unknown>
|
|
162
|
-
|
|
227
|
+
const lines = [manifestLine(manifest)]
|
|
228
|
+
// Reject reaps the session branch best-effort; a failed reap must never be
|
|
229
|
+
// silent (ALI-1043) — surface it with the exact cleanup command.
|
|
230
|
+
if (subcommand === 'reject' && manifest.actionType === 'repo_merge') {
|
|
231
|
+
const outcome = asRecord(manifest.outcome)
|
|
232
|
+
const branch = asRecord(manifest.proposedState).branch
|
|
233
|
+
if (outcome.branchDeleted === true) {
|
|
234
|
+
lines.push('session branch deleted')
|
|
235
|
+
} else if (typeof branch === 'string' && branch.length > 0) {
|
|
236
|
+
lines.push(
|
|
237
|
+
`warning: the session branch could not be deleted — remove it with: git push origin --delete ${branch}`
|
|
238
|
+
)
|
|
239
|
+
} else {
|
|
240
|
+
lines.push('warning: the session branch could not be deleted (branch name unavailable)')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
emit(io, { manifest }, lines.join('\n'))
|
|
163
244
|
return 0
|
|
164
245
|
}
|
|
165
246
|
|