vibepro 0.1.0-alpha.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/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.ja.md +448 -0
- package/README.md +520 -0
- package/agent-instructions/codex/AGENTS.vibepro.md +45 -0
- package/bin/vibepro.js +9 -0
- package/docs/assets/vibepro-header.png +0 -0
- package/package.json +51 -0
- package/skills/vibepro-diagnosis-packages/SKILL.md +133 -0
- package/skills/vibepro-human-review/SKILL.md +73 -0
- package/skills/vibepro-story-refactor/SKILL.md +89 -0
- package/skills/vibepro-workflow/SKILL.md +139 -0
- package/src/agent-harness-map.js +230 -0
- package/src/agent-harness-scanner.js +337 -0
- package/src/agent-review.js +2180 -0
- package/src/api-boundary-scanner.js +452 -0
- package/src/architecture-profiler.js +423 -0
- package/src/authorization-scoring.js +149 -0
- package/src/brainbase-importer.js +534 -0
- package/src/change-risk-classifier.js +195 -0
- package/src/check-packs.js +605 -0
- package/src/checkpoint-manager.js +233 -0
- package/src/cli.js +2213 -0
- package/src/code-quality-scanner.js +310 -0
- package/src/codex-manager.js +143 -0
- package/src/component-style-scanner.js +336 -0
- package/src/coverage-report.js +99 -0
- package/src/database-access-scanner.js +163 -0
- package/src/decision-records.js +315 -0
- package/src/design-modernize.js +1435 -0
- package/src/design-system.js +1732 -0
- package/src/diagnostic-engine.js +1945 -0
- package/src/diagram-requirement-resolver.js +194 -0
- package/src/doctor.js +677 -0
- package/src/environment-graph.js +424 -0
- package/src/execution-state.js +849 -0
- package/src/explore-evidence.js +425 -0
- package/src/flow-design-scanner.js +896 -0
- package/src/flow-verifier.js +887 -0
- package/src/gesture-interaction-scanner.js +330 -0
- package/src/graph-context.js +263 -0
- package/src/graphify-adapter.js +189 -0
- package/src/html-report.js +1035 -0
- package/src/journey-map.js +1299 -0
- package/src/language.js +48 -0
- package/src/lazy-pattern-detector.js +182 -0
- package/src/local-dev-scanner.js +135 -0
- package/src/managed-worktree-gate.js +187 -0
- package/src/managed-worktree.js +766 -0
- package/src/merge-manager.js +501 -0
- package/src/network-contract-scanner.js +442 -0
- package/src/nocodb-story-sync.js +386 -0
- package/src/oss-readiness-scanner.js +417 -0
- package/src/performance-evidence.js +756 -0
- package/src/performance-measurer.js +591 -0
- package/src/pr-manager.js +8220 -0
- package/src/presets.js +682 -0
- package/src/public-discovery-scanner.js +519 -0
- package/src/refactoring-delta-reporter.js +367 -0
- package/src/refactoring-opportunity-generator.js +797 -0
- package/src/regression-risk-scanner.js +146 -0
- package/src/repo-status.js +266 -0
- package/src/report-fingerprint.js +188 -0
- package/src/report-pr-body-prompt-template.md +108 -0
- package/src/report-pr-body-schema.json +95 -0
- package/src/report-store.js +135 -0
- package/src/report-validator.js +192 -0
- package/src/requirement-consistency.js +1066 -0
- package/src/runtime-info.js +134 -0
- package/src/self-dogfood-scanner.js +476 -0
- package/src/session-learning.js +164 -0
- package/src/skills-manager.js +157 -0
- package/src/spec-drift.js +378 -0
- package/src/spec-fingerprint.js +445 -0
- package/src/spec-prompt-template.md +155 -0
- package/src/spec-schema.json +219 -0
- package/src/spec-store.js +258 -0
- package/src/spec-validator.js +459 -0
- package/src/static-site-scanner.js +316 -0
- package/src/story-candidate-generator.js +85 -0
- package/src/story-catalog-generator.js +2813 -0
- package/src/story-html.js +156 -0
- package/src/story-manager.js +2144 -0
- package/src/story-task-generator.js +522 -0
- package/src/task-manager.js +1029 -0
- package/src/terminal-link-scanner.js +238 -0
- package/src/usage-report.js +417 -0
- package/src/verification-evidence.js +284 -0
- package/src/workspace.js +126 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { getWorkspaceDir, initWorkspace, readManifest, toWorkspaceRelative, writeManifest } from './workspace.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_BASE_ID = 'pfgza5aei6wboaq';
|
|
7
|
+
const DEFAULT_TABLE_ID = 'mjpg80jobkjo8lz';
|
|
8
|
+
const STORY_FIELD_TITLES = [
|
|
9
|
+
'Story ID',
|
|
10
|
+
'名前',
|
|
11
|
+
'ステータス',
|
|
12
|
+
'Horizon',
|
|
13
|
+
'View',
|
|
14
|
+
'Period',
|
|
15
|
+
'開始日',
|
|
16
|
+
'期限日'
|
|
17
|
+
];
|
|
18
|
+
const PUBLISH_FIELD_TITLES = ['Story ID', '説明'];
|
|
19
|
+
const DIAGNOSIS_START = '<!-- vibepro:diagnosis-sync:start -->';
|
|
20
|
+
const DIAGNOSIS_END = '<!-- vibepro:diagnosis-sync:end -->';
|
|
21
|
+
|
|
22
|
+
export async function syncStoriesFromNocoDB(repoRoot, options = {}) {
|
|
23
|
+
await initWorkspace(repoRoot);
|
|
24
|
+
const root = path.resolve(repoRoot);
|
|
25
|
+
const configPath = path.join(getWorkspaceDir(root), 'config.json');
|
|
26
|
+
const config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
27
|
+
const source = resolveNocoDBSource(config, options.env ?? process.env);
|
|
28
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
29
|
+
if (!fetchFn) throw new Error('fetch is not available in this Node.js runtime');
|
|
30
|
+
|
|
31
|
+
const schema = await fetchTableSchema(source, fetchFn);
|
|
32
|
+
validateStorySchema(schema);
|
|
33
|
+
const records = await fetchStoryRecords(source, fetchFn);
|
|
34
|
+
const stories = records
|
|
35
|
+
.filter((record) => !isArchived(record))
|
|
36
|
+
.map(normalizeStoryRecord)
|
|
37
|
+
.filter((story) => story.story_id);
|
|
38
|
+
|
|
39
|
+
config.brainbase = {
|
|
40
|
+
...(config.brainbase ?? {}),
|
|
41
|
+
story_source: {
|
|
42
|
+
type: 'nocodb',
|
|
43
|
+
base_id: source.baseId,
|
|
44
|
+
table_id: source.tableId,
|
|
45
|
+
synced_at: new Date().toISOString()
|
|
46
|
+
},
|
|
47
|
+
stories
|
|
48
|
+
};
|
|
49
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
50
|
+
|
|
51
|
+
return { stories, storySource: config.brainbase.story_source };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function publishStatusToNocoDB(repoRoot, options = {}) {
|
|
55
|
+
await initWorkspace(repoRoot);
|
|
56
|
+
const root = path.resolve(repoRoot);
|
|
57
|
+
const configPath = path.join(getWorkspaceDir(root), 'config.json');
|
|
58
|
+
const importStatePath = path.join(getWorkspaceDir(root), 'brainbase', 'import-state.json');
|
|
59
|
+
const config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
60
|
+
const importState = JSON.parse(await readFile(importStatePath, 'utf8'));
|
|
61
|
+
const source = resolveNocoDBSource(config, options.env ?? process.env);
|
|
62
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
63
|
+
if (!fetchFn) throw new Error('fetch is not available in this Node.js runtime');
|
|
64
|
+
|
|
65
|
+
const schema = await fetchTableSchema(source, fetchFn);
|
|
66
|
+
validatePublishSchema(schema);
|
|
67
|
+
const story = selectPublishStory(importState, options.storyId);
|
|
68
|
+
const record = await findStoryRecord(source, fetchFn, story.story_id);
|
|
69
|
+
const existingDescription = pick(record, '説明', 'description') ?? '';
|
|
70
|
+
const nextDescription = replaceDiagnosisSection(existingDescription, renderDiagnosisSection(importState));
|
|
71
|
+
|
|
72
|
+
if (options.dryRun) {
|
|
73
|
+
const preview = await writePublishPreview(root, {
|
|
74
|
+
importState,
|
|
75
|
+
story,
|
|
76
|
+
record,
|
|
77
|
+
existingDescription,
|
|
78
|
+
nextDescription
|
|
79
|
+
});
|
|
80
|
+
await recordPublishPreview(root, preview, importState);
|
|
81
|
+
return {
|
|
82
|
+
storyId: story.story_id,
|
|
83
|
+
recordId: getRecordId(record),
|
|
84
|
+
dryRun: true,
|
|
85
|
+
preview
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const backup = await writePublishBackup(root, {
|
|
90
|
+
importState,
|
|
91
|
+
story,
|
|
92
|
+
record,
|
|
93
|
+
existingDescription,
|
|
94
|
+
nextDescription
|
|
95
|
+
});
|
|
96
|
+
await patchStoryDescription(source, fetchFn, record, nextDescription);
|
|
97
|
+
const verifiedRecord = await findStoryRecord(source, fetchFn, story.story_id);
|
|
98
|
+
const verifiedDescription = pick(verifiedRecord, '説明', 'description') ?? '';
|
|
99
|
+
const publishResult = await writePublishResult(root, {
|
|
100
|
+
importState,
|
|
101
|
+
story,
|
|
102
|
+
record: verifiedRecord,
|
|
103
|
+
backup,
|
|
104
|
+
nextDescription,
|
|
105
|
+
verifiedDescription
|
|
106
|
+
});
|
|
107
|
+
await recordPublishResult(root, backup, publishResult, importState);
|
|
108
|
+
return {
|
|
109
|
+
storyId: story.story_id,
|
|
110
|
+
recordId: getRecordId(record),
|
|
111
|
+
backup,
|
|
112
|
+
publishResult
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function selectPublishStory(importState, storyId) {
|
|
117
|
+
const stories = Array.isArray(importState.stories) ? importState.stories : [];
|
|
118
|
+
if (storyId) {
|
|
119
|
+
const story = stories.find((item) => item.story_id === storyId);
|
|
120
|
+
if (!story) throw new Error(`Story ID is not included in portfolio dashboard import state: ${storyId}`);
|
|
121
|
+
return story;
|
|
122
|
+
}
|
|
123
|
+
const story = importState.story ?? stories[0];
|
|
124
|
+
if (!story?.story_id) throw new Error('portfolio dashboard import state does not contain a publishable story');
|
|
125
|
+
return story;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveNocoDBSource(config, env) {
|
|
129
|
+
const storySource = config.brainbase?.story_source ?? {};
|
|
130
|
+
const url = env.NOCODB_URL ?? storySource.url;
|
|
131
|
+
const token = env.NOCODB_TOKEN ?? storySource.token;
|
|
132
|
+
if (!url) throw new Error('NOCODB_URL is required to sync portfolio dashboard stories');
|
|
133
|
+
if (!token) throw new Error('NOCODB_TOKEN is required to sync portfolio dashboard stories');
|
|
134
|
+
return {
|
|
135
|
+
url: url.replace(/\/$/, ''),
|
|
136
|
+
token,
|
|
137
|
+
baseId: env.NOCODB_STORY_BASE_ID ?? storySource.base_id ?? DEFAULT_BASE_ID,
|
|
138
|
+
tableId: env.NOCODB_STORY_TABLE_ID ?? storySource.table_id ?? DEFAULT_TABLE_ID
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function fetchTableSchema(source, fetchFn) {
|
|
143
|
+
const response = await fetchFn(`${source.url}/api/v1/db/meta/tables/${source.tableId}`, {
|
|
144
|
+
headers: { 'xc-token': source.token }
|
|
145
|
+
});
|
|
146
|
+
return readResponseJson(response, 'NocoDB Story table schema fetch failed');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function fetchStoryRecords(source, fetchFn) {
|
|
150
|
+
const records = [];
|
|
151
|
+
let offset = 0;
|
|
152
|
+
const limit = 500;
|
|
153
|
+
|
|
154
|
+
while (true) {
|
|
155
|
+
const query = new URLSearchParams({ limit: String(limit), offset: String(offset) });
|
|
156
|
+
const response = await fetchFn(`${source.url}/api/v1/db/data/noco/${source.baseId}/${source.tableId}?${query}`, {
|
|
157
|
+
headers: { 'xc-token': source.token }
|
|
158
|
+
});
|
|
159
|
+
const body = await readResponseJson(response, 'NocoDB Story record fetch failed');
|
|
160
|
+
records.push(...(Array.isArray(body.list) ? body.list : []));
|
|
161
|
+
if (body.pageInfo?.isLastPage !== false && records.length >= (body.pageInfo?.totalRows ?? records.length)) break;
|
|
162
|
+
if (!Array.isArray(body.list) || body.list.length === 0) break;
|
|
163
|
+
offset += body.list.length;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return records;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function readResponseJson(response, message) {
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
throw new Error(`${message}: HTTP ${response.status}`);
|
|
172
|
+
}
|
|
173
|
+
return response.json();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateStorySchema(schema) {
|
|
177
|
+
const titles = new Set((schema.columns ?? []).map((column) => column.title));
|
|
178
|
+
const missing = STORY_FIELD_TITLES.filter((title) => !titles.has(title));
|
|
179
|
+
if (missing.length > 0) {
|
|
180
|
+
throw new Error(`NocoDB Story table schema is missing columns: ${missing.join(', ')}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function validatePublishSchema(schema) {
|
|
185
|
+
const titles = new Set((schema.columns ?? []).map((column) => column.title));
|
|
186
|
+
const missing = PUBLISH_FIELD_TITLES.filter((title) => !titles.has(title));
|
|
187
|
+
if (missing.length > 0) {
|
|
188
|
+
throw new Error(`NocoDB Story table schema is missing columns: ${missing.join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function findStoryRecord(source, fetchFn, storyId) {
|
|
193
|
+
const query = new URLSearchParams({ where: `(Story ID,eq,${storyId})`, limit: '1' });
|
|
194
|
+
const response = await fetchFn(`${source.url}/api/v1/db/data/noco/${source.baseId}/${source.tableId}?${query}`, {
|
|
195
|
+
headers: { 'xc-token': source.token }
|
|
196
|
+
});
|
|
197
|
+
const body = await readResponseJson(response, 'NocoDB Story record lookup failed');
|
|
198
|
+
const record = Array.isArray(body.list) ? body.list[0] : null;
|
|
199
|
+
if (!record) throw new Error(`NocoDB Story record not found: ${storyId}`);
|
|
200
|
+
return record;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function patchStoryDescription(source, fetchFn, record, description) {
|
|
204
|
+
const recordId = getRecordId(record);
|
|
205
|
+
if (!recordId) throw new Error('NocoDB Story record does not contain an Id');
|
|
206
|
+
const response = await fetchFn(`${source.url}/api/v1/db/data/noco/${source.baseId}/${source.tableId}/${recordId}`, {
|
|
207
|
+
method: 'PATCH',
|
|
208
|
+
headers: {
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
'xc-token': source.token
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({ 説明: description })
|
|
213
|
+
});
|
|
214
|
+
await readResponseJson(response, 'NocoDB Story description update failed');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getRecordId(record) {
|
|
218
|
+
return record.Id ?? record.id ?? record.ID ?? record['番号'] ?? null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function replaceDiagnosisSection(description, section) {
|
|
222
|
+
const blockPattern = new RegExp(`${escapeRegExp(DIAGNOSIS_START)}[\\s\\S]*?${escapeRegExp(DIAGNOSIS_END)}`);
|
|
223
|
+
const nextBlock = `${DIAGNOSIS_START}\n${section}\n${DIAGNOSIS_END}`;
|
|
224
|
+
if (blockPattern.test(description)) {
|
|
225
|
+
return description.replace(blockPattern, nextBlock);
|
|
226
|
+
}
|
|
227
|
+
const prefix = description.trimEnd();
|
|
228
|
+
return `${prefix}${prefix ? '\n\n' : ''}${nextBlock}\n`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function writePublishBackup(repoRoot, { importState, story, record, existingDescription, nextDescription }) {
|
|
232
|
+
const brainbaseDir = path.join(getWorkspaceDir(repoRoot), 'brainbase');
|
|
233
|
+
await mkdir(brainbaseDir, { recursive: true });
|
|
234
|
+
const backup = {
|
|
235
|
+
schema_version: '0.1.0',
|
|
236
|
+
generated_at: new Date().toISOString(),
|
|
237
|
+
story_id: story.story_id,
|
|
238
|
+
record_id: getRecordId(record),
|
|
239
|
+
latest_run_id: importState.latest_run.run_id,
|
|
240
|
+
existing_description: existingDescription,
|
|
241
|
+
next_description: nextDescription
|
|
242
|
+
};
|
|
243
|
+
const backupJsonPath = path.join(brainbaseDir, 'publish-backup.json');
|
|
244
|
+
await writeFile(backupJsonPath, `${JSON.stringify(backup, null, 2)}\n`);
|
|
245
|
+
return { ...backup, backupJsonPath };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function writePublishResult(repoRoot, { importState, story, record, backup, nextDescription, verifiedDescription }) {
|
|
249
|
+
const brainbaseDir = path.join(getWorkspaceDir(repoRoot), 'brainbase');
|
|
250
|
+
await mkdir(brainbaseDir, { recursive: true });
|
|
251
|
+
const descriptionMatchesExpected = verifiedDescription === nextDescription;
|
|
252
|
+
if (!descriptionMatchesExpected) {
|
|
253
|
+
throw new Error(`NocoDB Story description verification failed: ${story.story_id}`);
|
|
254
|
+
}
|
|
255
|
+
const publishResult = {
|
|
256
|
+
schema_version: '0.1.0',
|
|
257
|
+
published_at: new Date().toISOString(),
|
|
258
|
+
story_id: story.story_id,
|
|
259
|
+
record_id: getRecordId(record),
|
|
260
|
+
latest_run_id: importState.latest_run.run_id,
|
|
261
|
+
gate_status: importState.latest_run.gate_status,
|
|
262
|
+
verified: true,
|
|
263
|
+
description_matches_expected: descriptionMatchesExpected,
|
|
264
|
+
updated_fields: ['説明'],
|
|
265
|
+
status_changed: false,
|
|
266
|
+
backup_json: toWorkspaceRelative(repoRoot, backup.backupJsonPath)
|
|
267
|
+
};
|
|
268
|
+
const resultJsonPath = path.join(brainbaseDir, 'publish-result.json');
|
|
269
|
+
await writeFile(resultJsonPath, `${JSON.stringify(publishResult, null, 2)}\n`);
|
|
270
|
+
return { ...publishResult, resultJsonPath };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function recordPublishResult(repoRoot, backup, publishResult, importState) {
|
|
274
|
+
const manifest = await readManifest(repoRoot);
|
|
275
|
+
manifest.brainbase = {
|
|
276
|
+
...(manifest.brainbase ?? {}),
|
|
277
|
+
last_publish_result: {
|
|
278
|
+
published_at: publishResult.published_at,
|
|
279
|
+
latest_run_id: importState.latest_run.run_id,
|
|
280
|
+
story_id: publishResult.story_id,
|
|
281
|
+
verified: publishResult.verified,
|
|
282
|
+
backup_json: toWorkspaceRelative(repoRoot, backup.backupJsonPath),
|
|
283
|
+
result_json: toWorkspaceRelative(repoRoot, publishResult.resultJsonPath)
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
await writeManifest(repoRoot, manifest);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function writePublishPreview(repoRoot, { importState, story, record, existingDescription, nextDescription }) {
|
|
290
|
+
const brainbaseDir = path.join(getWorkspaceDir(repoRoot), 'brainbase');
|
|
291
|
+
await mkdir(brainbaseDir, { recursive: true });
|
|
292
|
+
const preview = {
|
|
293
|
+
schema_version: '0.1.0',
|
|
294
|
+
dry_run: true,
|
|
295
|
+
generated_at: new Date().toISOString(),
|
|
296
|
+
story_id: story.story_id,
|
|
297
|
+
record_id: getRecordId(record),
|
|
298
|
+
latest_run_id: importState.latest_run.run_id,
|
|
299
|
+
gate_status: importState.latest_run.gate_status,
|
|
300
|
+
existing_description: existingDescription,
|
|
301
|
+
next_description: nextDescription
|
|
302
|
+
};
|
|
303
|
+
const previewJsonPath = path.join(brainbaseDir, 'publish-preview.json');
|
|
304
|
+
const previewMarkdownPath = path.join(brainbaseDir, 'publish-preview.md');
|
|
305
|
+
await writeFile(previewJsonPath, `${JSON.stringify(preview, null, 2)}\n`);
|
|
306
|
+
await writeFile(previewMarkdownPath, renderPublishPreview(preview));
|
|
307
|
+
return { ...preview, previewJsonPath, previewMarkdownPath };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function recordPublishPreview(repoRoot, preview, importState) {
|
|
311
|
+
const manifest = await readManifest(repoRoot);
|
|
312
|
+
manifest.brainbase = {
|
|
313
|
+
...(manifest.brainbase ?? {}),
|
|
314
|
+
last_publish_preview: {
|
|
315
|
+
generated_at: preview.generated_at,
|
|
316
|
+
latest_run_id: importState.latest_run.run_id,
|
|
317
|
+
story_id: preview.story_id,
|
|
318
|
+
preview_json: toWorkspaceRelative(repoRoot, preview.previewJsonPath),
|
|
319
|
+
preview_markdown: toWorkspaceRelative(repoRoot, preview.previewMarkdownPath)
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
await writeManifest(repoRoot, manifest);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function renderPublishPreview(preview) {
|
|
326
|
+
return `# VibePro診断同期プレビュー
|
|
327
|
+
|
|
328
|
+
| 項目 | 内容 |
|
|
329
|
+
|------|------|
|
|
330
|
+
| Story ID | ${preview.story_id} |
|
|
331
|
+
| Record ID | ${preview.record_id} |
|
|
332
|
+
| Gate | ${preview.gate_status} |
|
|
333
|
+
| Dry Run | true |
|
|
334
|
+
|
|
335
|
+
PATCHは実行していない。
|
|
336
|
+
|
|
337
|
+
## 更新後の説明
|
|
338
|
+
|
|
339
|
+
\`\`\`md
|
|
340
|
+
${preview.next_description}
|
|
341
|
+
\`\`\`
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function renderDiagnosisSection(importState) {
|
|
346
|
+
const findings = importState.findings ?? [];
|
|
347
|
+
return [
|
|
348
|
+
'## VibePro診断同期',
|
|
349
|
+
'',
|
|
350
|
+
`- Run ID: ${importState.latest_run.run_id}`,
|
|
351
|
+
`- Gate: ${importState.latest_run.gate_status}`,
|
|
352
|
+
`- graphify nodes: ${importState.signals.graphify.node_count}`,
|
|
353
|
+
`- graphify edges: ${importState.signals.graphify.edge_count}`,
|
|
354
|
+
`- 検出事項: ${findings.length}件`,
|
|
355
|
+
findings.length === 0
|
|
356
|
+
? '- 主な検出事項: なし'
|
|
357
|
+
: `- 主な検出事項: ${findings.map((finding) => `${finding.id} ${finding.title}`).join(', ')}`
|
|
358
|
+
].join('\n');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function escapeRegExp(text) {
|
|
362
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function normalizeStoryRecord(record) {
|
|
366
|
+
return {
|
|
367
|
+
story_id: pick(record, 'Story ID', 'story_id'),
|
|
368
|
+
title: pick(record, '名前', 'name'),
|
|
369
|
+
ssot: 'NocoDB',
|
|
370
|
+
horizon: pick(record, 'Horizon', 'horizon'),
|
|
371
|
+
view: pick(record, 'View', 'view'),
|
|
372
|
+
period: pick(record, 'Period', 'period'),
|
|
373
|
+
started_at: pick(record, '開始日', 'started_at'),
|
|
374
|
+
due_at: pick(record, '期限日', 'due_at')
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function isArchived(record) {
|
|
379
|
+
const status = pick(record, 'ステータス', 'status');
|
|
380
|
+
return status === 'archived' || status === 'アーカイブ';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function pick(record, titleKey, columnKey) {
|
|
384
|
+
const value = record[titleKey] ?? record[columnKey] ?? null;
|
|
385
|
+
return value === '' ? null : value;
|
|
386
|
+
}
|