@mmerterden/multi-agent-pipeline 20.11.1 → 20.13.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/CHANGELOG.md +51 -0
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/docs/facts.json +2 -2
- package/install/_common.mjs +1 -1
- package/install/_plugin-skills.mjs +13 -9
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +9 -0
- package/install/codex.mjs +12 -12
- package/manifest.json +74 -67
- package/package.json +5 -5
- package/pipeline/agents/code-reviewer.md +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +7 -3
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +57 -7
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -0
- package/pipeline/commands/multi-agent/autopilot-off/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/doctor/SKILL.md +6 -0
- package/pipeline/commands/multi-agent/feedback/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/forget/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/model/SKILL.md +7 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/prune-prompts/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +5 -0
- package/pipeline/commands/multi-agent/review-analysis/SKILL.md +7 -2
- package/pipeline/commands/multi-agent/sync/SKILL.md +5 -6
- package/pipeline/lib/agent-model-sync.mjs +95 -0
- package/pipeline/lib/analysis-jira-write.sh +81 -27
- package/pipeline/lib/analysis-quality.mjs +261 -0
- package/pipeline/lib/analysis-sections.mjs +105 -0
- package/pipeline/lib/jira-epic-link.sh +54 -0
- package/pipeline/lib/model-rung.sh +7 -0
- package/pipeline/multi-agent-refs/analysis/locked.md +4 -4
- package/pipeline/multi-agent-refs/analysis/redesign.md +3 -1
- package/pipeline/multi-agent-refs/analysis/render.md +20 -21
- package/pipeline/multi-agent-refs/analysis/resolve.md +1 -1
- package/pipeline/multi-agent-refs/analysis/review.md +16 -3
- package/pipeline/multi-agent-refs/analysis/synthesis.md +10 -2
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +44 -9
- package/pipeline/multi-agent-refs/analysis-template.md +21 -14
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -0
- package/pipeline/multi-agent-refs/features/analysis-jira.md +69 -19
- package/pipeline/multi-agent-refs/features/doctor.md +10 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +10 -0
- package/pipeline/multi-agent-refs/features/phone-api.md +1 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
- package/pipeline/multi-agent-refs/features/usage-reporting.md +13 -0
- package/pipeline/schemas/analysis-spec.schema.json +22 -0
- package/pipeline/schemas/prefs.schema.json +22 -1
- package/pipeline/scripts/analysis-story-body.mjs +210 -0
- package/pipeline/scripts/analysis-story-tree.mjs +73 -12
- package/pipeline/scripts/analysis-tickets-writeback.mjs +101 -0
- package/pipeline/scripts/build-references.mjs +14 -1
- package/pipeline/scripts/confluence-readback.mjs +148 -0
- package/pipeline/scripts/doctor.mjs +48 -0
- package/pipeline/scripts/jira-wiki-escape.mjs +40 -0
- package/pipeline/scripts/migrate-prefs.mjs +14 -0
- package/pipeline/scripts/unattended_policy.py +12 -5
- package/pipeline/scripts/usage-report.mjs +84 -9
- package/pipeline/scripts/validate-analysis-doc.mjs +120 -70
- package/pipeline/skills/.skill-manifest.json +8 -8
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +6 -1
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +56 -6
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +6 -0
- package/pipeline/skills/shared/core/multi-agent-doctor/SKILL.md +6 -0
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +5 -0
- package/pipeline/skills/shared/core/multi-agent-review-analysis/SKILL.md +5 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analysis-quality.mjs - document-quality checks on a rendered analysis
|
|
3
|
+
* document: every use case says how it fails, the document does not contradict
|
|
4
|
+
* itself, each layer carries only its own kind of content, and a design frame
|
|
5
|
+
* belongs to a use case.
|
|
6
|
+
*
|
|
7
|
+
* Each check takes the document's lines and returns `{errors, warns}` so the
|
|
8
|
+
* validator stays the only place that prints and decides the exit code.
|
|
9
|
+
*
|
|
10
|
+
* @module pipeline/lib/analysis-quality
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sectionBody, tableDataRows } from "./analysis-sections.mjs";
|
|
14
|
+
|
|
15
|
+
const cellsOf = (row) =>
|
|
16
|
+
row
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/^\||\|$/g, "")
|
|
19
|
+
.split("|")
|
|
20
|
+
.map((c) => c.trim());
|
|
21
|
+
const unbold = (s) => s.replace(/\*\*/g, "").trim();
|
|
22
|
+
const isHeading = (l) => /^#{1,6}\s/.test(l);
|
|
23
|
+
const isNumberedHeading = (l) => /^#{2,4}\s+\d+(\.\d+)*\.?\s/.test(l);
|
|
24
|
+
const UC_HEADING = /\bUC-\d{2,3}\b/;
|
|
25
|
+
|
|
26
|
+
const MAIN_FLOW = ["Ana Akış", "Ana Akis", "Main Flow"];
|
|
27
|
+
const EXCEPTION_FLOW = ["İstisna Akışı", "Istisna Akisi", "Exception Flow"];
|
|
28
|
+
const EXPLICIT_NONE = /^(yok|none)\s*:\s*\S/i;
|
|
29
|
+
|
|
30
|
+
/** Use-case sections: from a heading naming `UC-NNN` up to the next numbered heading. */
|
|
31
|
+
function useCaseBlocks(lines) {
|
|
32
|
+
const blocks = [];
|
|
33
|
+
let current = null;
|
|
34
|
+
for (const l of lines) {
|
|
35
|
+
if (isHeading(l)) {
|
|
36
|
+
const uc = l.match(UC_HEADING);
|
|
37
|
+
if (uc && isNumberedHeading(l)) {
|
|
38
|
+
current = { id: uc[0], lines: [] };
|
|
39
|
+
blocks.push(current);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (isNumberedHeading(l) || /^#\s/.test(l)) current = null;
|
|
43
|
+
}
|
|
44
|
+
if (current) current.lines.push(l);
|
|
45
|
+
}
|
|
46
|
+
return blocks;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fieldRow(lines, names) {
|
|
50
|
+
for (const l of lines) {
|
|
51
|
+
if (!l.trim().startsWith("|")) continue;
|
|
52
|
+
const c = cellsOf(l);
|
|
53
|
+
if (c.length >= 2 && names.some((n) => unbold(c[0]).toLowerCase() === n.toLowerCase())) {
|
|
54
|
+
return c[1];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A use case with a main flow states its exception flow. Missing is an error on
|
|
62
|
+
* a final document and a warning on a draft; "-" or empty is a warning;
|
|
63
|
+
* "Yok: <reason>" / "None: <reason>" is an explicit answer.
|
|
64
|
+
*/
|
|
65
|
+
export function exceptionFlow(lines, { final = false } = {}) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
const warns = [];
|
|
68
|
+
for (const uc of useCaseBlocks(lines)) {
|
|
69
|
+
if (fieldRow(uc.lines, MAIN_FLOW) === undefined) continue;
|
|
70
|
+
const v = fieldRow(uc.lines, EXCEPTION_FLOW);
|
|
71
|
+
if (v === undefined) {
|
|
72
|
+
const msg = `${uc.id} has a main flow but no exception flow row (İstisna Akışı / Exception Flow)`;
|
|
73
|
+
(final ? errors : warns).push(msg);
|
|
74
|
+
} else if (!v || /^\p{Pd}$/u.test(v)) {
|
|
75
|
+
warns.push(
|
|
76
|
+
`${uc.id} exception flow is empty; write the failure path, or "Yok: <reason>" when there is none`,
|
|
77
|
+
);
|
|
78
|
+
} else if (/^(yok|none)\.?$/i.test(v) && !EXPLICIT_NONE.test(v)) {
|
|
79
|
+
warns.push(`${uc.id} exception flow says none without a reason ("Yok: <reason>")`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { errors, warns };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const normalize = (s) =>
|
|
86
|
+
s
|
|
87
|
+
.toLocaleLowerCase("tr")
|
|
88
|
+
.replace(/[.,;:!?"'`*_]/g, "")
|
|
89
|
+
.replace(/\s+/g, " ")
|
|
90
|
+
.trim();
|
|
91
|
+
|
|
92
|
+
const GOALS_KEYWORDS = [
|
|
93
|
+
"Hedefler ve Karşı Hedefler",
|
|
94
|
+
"Hedefler ve Karsi Hedefler",
|
|
95
|
+
"Goals and Non-Goals",
|
|
96
|
+
"Goals",
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
/** The same item on both sides of the goals table. */
|
|
100
|
+
export function scopeOverlap(lines) {
|
|
101
|
+
const errors = [];
|
|
102
|
+
const body = sectionBody(lines, GOALS_KEYWORDS);
|
|
103
|
+
if (!body) return { errors, warns: [] };
|
|
104
|
+
const goals = new Map();
|
|
105
|
+
const nonGoals = new Set();
|
|
106
|
+
for (const row of tableDataRows(body)) {
|
|
107
|
+
const c = cellsOf(row);
|
|
108
|
+
if (c.length < 2) continue;
|
|
109
|
+
if (c[0] && c[0] !== "-") goals.set(normalize(c[0]), c[0]);
|
|
110
|
+
if (c[1] && c[1] !== "-") nonGoals.add(normalize(c[1]));
|
|
111
|
+
}
|
|
112
|
+
for (const [key] of goals) {
|
|
113
|
+
if (key && nonGoals.has(key)) {
|
|
114
|
+
errors.push(`"${key}" is both in scope and out of scope in the goals table (Locked 14)`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { errors, warns: [] };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const CITABLE = [
|
|
121
|
+
/\bBR-[a-z0-9]+(?:-[a-z0-9]+)*-\d+\b/gi,
|
|
122
|
+
/\bCB-[a-z0-9]+(?:-[a-z0-9]+)*-\d+\b/gi,
|
|
123
|
+
/\b(?:IG|FG)-\d{2,3}\b/g,
|
|
124
|
+
/\bUC-\d{2,3}\b/g,
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ids the text cites but never defines. An id is defined by the first cell of a
|
|
129
|
+
* table row or by a heading. Section 20 `AS-NN` ids have their own pairing check.
|
|
130
|
+
*/
|
|
131
|
+
export function citedUndefined(lines) {
|
|
132
|
+
const defined = new Set();
|
|
133
|
+
const cited = new Set();
|
|
134
|
+
for (const l of lines) {
|
|
135
|
+
const t = l.trim();
|
|
136
|
+
const sources = [];
|
|
137
|
+
if (isHeading(t)) {
|
|
138
|
+
sources.push([t, defined]);
|
|
139
|
+
} else if (t.startsWith("|")) {
|
|
140
|
+
const c = cellsOf(t);
|
|
141
|
+
sources.push([unbold(c[0] || ""), defined], [c.slice(1).join(" | "), cited]);
|
|
142
|
+
} else {
|
|
143
|
+
sources.push([t, cited]);
|
|
144
|
+
}
|
|
145
|
+
for (const [chunk, set] of sources) {
|
|
146
|
+
for (const re of CITABLE) {
|
|
147
|
+
for (const m of chunk.matchAll(re)) set.add(m[0].toUpperCase());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const errors = [...cited]
|
|
152
|
+
.filter((id) => !defined.has(id))
|
|
153
|
+
.sort()
|
|
154
|
+
.map((id) => `${id} is cited but never defined (no table row or heading names it)`);
|
|
155
|
+
return { errors, warns: [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const FIGMA_FRAME = /https:\/\/(?:www\.)?figma\.com\/[^\s)|\]]*node-id=([0-9]+[-:][0-9]+)/g;
|
|
159
|
+
const REFERENCES = /Referans|References/i;
|
|
160
|
+
|
|
161
|
+
/** A Figma frame linked outside every use case and outside the references (corporate). */
|
|
162
|
+
export function designFrameUnmapped(lines) {
|
|
163
|
+
const warns = [];
|
|
164
|
+
let inUc = false;
|
|
165
|
+
let inRefs = false;
|
|
166
|
+
for (const l of lines) {
|
|
167
|
+
if (isHeading(l)) {
|
|
168
|
+
if (isNumberedHeading(l) || /^#\s/.test(l)) {
|
|
169
|
+
inUc = UC_HEADING.test(l) && isNumberedHeading(l);
|
|
170
|
+
inRefs = REFERENCES.test(l) && /^##\s/.test(l);
|
|
171
|
+
} else if (/^##\s/.test(l)) {
|
|
172
|
+
inUc = false;
|
|
173
|
+
inRefs = false;
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (inUc || inRefs) continue;
|
|
178
|
+
for (const m of l.matchAll(FIGMA_FRAME)) {
|
|
179
|
+
warns.push(
|
|
180
|
+
`design frame ${m[1]} is linked outside every use case; list it under the use case it serves`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { errors: [], warns };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const PART_A = new Set(["1", "2", "3", "4", "18", "19", "20"]);
|
|
188
|
+
const REDESIGN_EVIDENCE = [
|
|
189
|
+
"Mevcut Davranış",
|
|
190
|
+
"Mevcut Davranis",
|
|
191
|
+
"Current Behaviour",
|
|
192
|
+
"Current Behavior",
|
|
193
|
+
"Endpoint Eşlemesi",
|
|
194
|
+
"Endpoint Eslemesi",
|
|
195
|
+
"Endpoint Mapping",
|
|
196
|
+
];
|
|
197
|
+
const CODE_PATH =
|
|
198
|
+
/[\w.-]+(?:\/[\w.-]+)+\.(?:swift|kt|kts|java|tsx?|jsx?|mjs|py|mm?|go|rb|cs|dart)\b/g;
|
|
199
|
+
const ENDPOINT = /\b(?:GET|POST|PUT|PATCH|DELETE)\s+\/[\w{}/.-]*/g;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* File paths and endpoints in the business layer (Part A: sections 1-4, 18-20) of
|
|
203
|
+
* the global profile. Collapsible `<details>` blocks and the redesign evidence
|
|
204
|
+
* tables are where such evidence belongs, so they are not read.
|
|
205
|
+
*/
|
|
206
|
+
export function businessLayerTechnical(lines) {
|
|
207
|
+
const warns = [];
|
|
208
|
+
let section = null;
|
|
209
|
+
let skip = false;
|
|
210
|
+
let details = 0;
|
|
211
|
+
for (const l of lines) {
|
|
212
|
+
const top = l.match(/^##\s+(\d+)\.?\s/);
|
|
213
|
+
if (top) {
|
|
214
|
+
section = top[1];
|
|
215
|
+
skip = false;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (/^#\s/.test(l)) {
|
|
219
|
+
section = null;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (/^#{3,4}\s/.test(l)) {
|
|
223
|
+
skip = REDESIGN_EVIDENCE.some((k) => l.includes(k));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
details += (l.match(/<details\b/gi) || []).length;
|
|
227
|
+
const closes = (l.match(/<\/details>/gi) || []).length;
|
|
228
|
+
const inDetails = details > 0;
|
|
229
|
+
details = Math.max(0, details - closes);
|
|
230
|
+
if (!section || !PART_A.has(section) || skip || inDetails) continue;
|
|
231
|
+
for (const re of [CODE_PATH, ENDPOINT]) {
|
|
232
|
+
for (const m of l.matchAll(re)) {
|
|
233
|
+
warns.push(
|
|
234
|
+
`Section ${section} (business layer) names "${m[0].trim()}"; move it to Part B/C or into a <details> evidence block`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { errors: [], warns };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const PANEL = /^>\s*\*\*(Missing inputs|Eksik girdiler)\s*:?\s*\*\*/i;
|
|
243
|
+
|
|
244
|
+
/** A final document that still lists missing inputs. */
|
|
245
|
+
export function missingInputsFinal(lines, { final = false } = {}) {
|
|
246
|
+
if (!final) return { errors: [], warns: [] };
|
|
247
|
+
const start = lines.findIndex((l) => PANEL.test(l.trim()));
|
|
248
|
+
if (start < 0) return { errors: [], warns: [] };
|
|
249
|
+
let items = 0;
|
|
250
|
+
for (const l of lines.slice(start + 1)) {
|
|
251
|
+
const t = l.trim();
|
|
252
|
+
if (!t.startsWith(">")) break;
|
|
253
|
+
if (/^>\s*[-*]\s+\S/.test(t)) items++;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
errors: [],
|
|
257
|
+
warns: items
|
|
258
|
+
? [`the document is final but its missing inputs panel lists ${items} input(s)`]
|
|
259
|
+
: [],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analysis-sections.mjs - locate sections and table rows in a rendered
|
|
3
|
+
* analysis document.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the document validator and the story-tree planner, so both read a
|
|
6
|
+
* section the same way. Sections are found by heading keyword, not by number:
|
|
7
|
+
* the global and corporate profiles number the same content differently, and a
|
|
8
|
+
* rendered document keeps canonical numbers with gaps.
|
|
9
|
+
*
|
|
10
|
+
* @module pipeline/lib/analysis-sections
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Body lines of the first numbered heading at one of `levels` whose text holds
|
|
15
|
+
* one of `keywords`. Closed by the next heading at the same level or shallower,
|
|
16
|
+
* so the last sub-section of a parent does not run into the following section.
|
|
17
|
+
*
|
|
18
|
+
* @param {string[]} lines
|
|
19
|
+
* @param {string[]} keywords
|
|
20
|
+
* @param {number|number[]} levels
|
|
21
|
+
* @returns {string[]|null}
|
|
22
|
+
*/
|
|
23
|
+
export function sectionBody(lines, keywords, levels = 2) {
|
|
24
|
+
for (const level of [].concat(levels)) {
|
|
25
|
+
const open = new RegExp(`^#{${level}}\\s+\\d+(\\.\\d+)*\\.?\\s`);
|
|
26
|
+
const close = new RegExp(`^#{1,${level}}\\s`);
|
|
27
|
+
const start = lines.findIndex((l) => open.test(l) && keywords.some((k) => l.includes(k)));
|
|
28
|
+
if (start < 0) continue;
|
|
29
|
+
let end = lines.length;
|
|
30
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
31
|
+
if (close.test(lines[i])) {
|
|
32
|
+
end = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines.slice(start + 1, end);
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Data rows of the tables in `bodyLines`: the pipe-rows after a separator row,
|
|
43
|
+
* which is the only shape that tells them from the header without reading cells.
|
|
44
|
+
*
|
|
45
|
+
* @param {string[]} bodyLines
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
export function tableDataRows(bodyLines) {
|
|
49
|
+
const rows = [];
|
|
50
|
+
let afterSeparator = false;
|
|
51
|
+
for (const line of bodyLines) {
|
|
52
|
+
const t = line.trim();
|
|
53
|
+
if (!t.startsWith("|")) {
|
|
54
|
+
afterSeparator = false;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (/^\|[\s\-:|]+\|$/.test(t)) {
|
|
58
|
+
afterSeparator = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (afterSeparator) rows.push(t);
|
|
62
|
+
}
|
|
63
|
+
return rows;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const CB_KEYWORDS = ["Mevcut Davranış", "Mevcut Davranis", "Current Behaviour", "Current Behavior"];
|
|
67
|
+
const EP_KEYWORDS = ["Endpoint Eşlemesi", "Endpoint Eslemesi", "Endpoint Mapping"];
|
|
68
|
+
const DL_KEYWORDS = ["Fark Listesi", "Difference List"];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Where each redesign artefact lives per profile. The global profile has them as
|
|
72
|
+
* sub-sections 4.5, 4.6 and 9.5; the corporate profile nests them under its own
|
|
73
|
+
* spine (2.1.1, 2.3.1 and the endpoint mapping under Section 5 service details).
|
|
74
|
+
*/
|
|
75
|
+
export const REDESIGN_SECTIONS = {
|
|
76
|
+
global: {
|
|
77
|
+
cb: { keywords: CB_KEYWORDS, levels: [3], label: "4.5 Current Behaviour" },
|
|
78
|
+
ep: { keywords: EP_KEYWORDS, levels: [3], label: "4.6 Endpoint Mapping" },
|
|
79
|
+
dl: { keywords: DL_KEYWORDS, levels: [3], label: "9.5 Difference List" },
|
|
80
|
+
},
|
|
81
|
+
corporate: {
|
|
82
|
+
cb: { keywords: CB_KEYWORDS, levels: [4], label: "2.1.1 Mevcut Davranış / Current Behaviour" },
|
|
83
|
+
ep: {
|
|
84
|
+
keywords: EP_KEYWORDS,
|
|
85
|
+
levels: [4],
|
|
86
|
+
label: "5.(N+4).1 Endpoint Eşlemesi / Endpoint Mapping",
|
|
87
|
+
},
|
|
88
|
+
dl: { keywords: DL_KEYWORDS, levels: [4], label: "2.3.1 Fark Listesi / Difference List" },
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The three redesign artefact bodies for a profile, plus their labels.
|
|
94
|
+
*
|
|
95
|
+
* @param {string[]} lines
|
|
96
|
+
* @param {string} profile
|
|
97
|
+
*/
|
|
98
|
+
export function redesignSections(lines, profile) {
|
|
99
|
+
const spec = REDESIGN_SECTIONS[profile] || REDESIGN_SECTIONS.global;
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const [key, s] of Object.entries(spec)) {
|
|
102
|
+
out[key] = { body: sectionBody(lines, s.keywords, s.levels), label: s.label };
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# jira-epic-link.sh - which fields link a new story to an existing epic.
|
|
3
|
+
#
|
|
4
|
+
# Jira links a story to its epic in two different ways. Cloud uses the standard
|
|
5
|
+
# `parent` field; Server and Data Center use an "Epic Link" custom field whose id
|
|
6
|
+
# differs per site. The id is discovered from the field list by its schema type,
|
|
7
|
+
# never assumed. When no way is found the story is created without a link and a
|
|
8
|
+
# warning says so, rather than writing a guessed field.
|
|
9
|
+
#
|
|
10
|
+
# Pure: the caller fetches /rest/api/2/serverInfo and /rest/api/2/field and
|
|
11
|
+
# passes the JSON in, so the decision is testable offline.
|
|
12
|
+
#
|
|
13
|
+
# epic_link_fields <mode> <epicKey> <fieldId> <serverInfoJson> <fieldsJson>
|
|
14
|
+
# mode: auto | parent | epicLinkField | none
|
|
15
|
+
# Prints a JSON object to merge into the create body's `fields`.
|
|
16
|
+
|
|
17
|
+
EPIC_LINK_SCHEMA="com.pyxis.greenhopper.jira:gh-epic-link"
|
|
18
|
+
|
|
19
|
+
epic_link_fields() {
|
|
20
|
+
local mode="${1:-auto}" epic="$2" field_id="$3" server="$4" fields="$5" deployment found
|
|
21
|
+
command -v jq >/dev/null 2>&1 || { echo "ERR: jq is required to link an epic" >&2; return 3; }
|
|
22
|
+
if [ -z "$epic" ] || [ "$mode" = "none" ]; then
|
|
23
|
+
printf '{}\n'
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
case "$mode" in
|
|
27
|
+
parent)
|
|
28
|
+
jq -cn --arg e "$epic" '{parent: {key: $e}}'
|
|
29
|
+
return 0
|
|
30
|
+
;;
|
|
31
|
+
epicLinkField)
|
|
32
|
+
if [ -z "$field_id" ]; then
|
|
33
|
+
echo "WARN: epicLinkField mode needs issueTree.epicLinkFieldId; the epic is not linked" >&2
|
|
34
|
+
printf '{}\n'
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
jq -cn --arg f "$field_id" --arg e "$epic" '{($f): $e}'
|
|
38
|
+
return 0
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
deployment="$(printf '%s' "$server" | jq -r '.deploymentType // empty' 2>/dev/null || true)"
|
|
42
|
+
if [ "$deployment" = "Cloud" ]; then
|
|
43
|
+
jq -cn --arg e "$epic" '{parent: {key: $e}}'
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
found="${field_id:-$(printf '%s' "$fields" | jq -r --arg s "$EPIC_LINK_SCHEMA" \
|
|
47
|
+
'[.[]? | select(.schema.custom == $s) | .id] | first // empty' 2>/dev/null || true)}"
|
|
48
|
+
if [ -z "$found" ]; then
|
|
49
|
+
echo "WARN: no epic-link field on this site; the stories are created without an epic" >&2
|
|
50
|
+
printf '{}\n'
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
jq -cn --arg f "$found" --arg e "$epic" '{($f): $e}'
|
|
54
|
+
}
|
|
@@ -128,6 +128,13 @@ fi
|
|
|
128
128
|
mv "$TMP" "$PREFS"
|
|
129
129
|
trap - EXIT
|
|
130
130
|
|
|
131
|
+
# Agents spawned without an explicit model run on their frontmatter, so the
|
|
132
|
+
# installed fable-tier agents follow the switch in the same step.
|
|
133
|
+
if command -v node >/dev/null 2>&1 && [ -f "$_MA_MR_HERE/agent-model-sync.mjs" ]; then
|
|
134
|
+
node "$_MA_MR_HERE/agent-model-sync.mjs" --prefs "$PREFS" ||
|
|
135
|
+
echo "model-rung: installed agents could not be updated; run node $_MA_MR_HERE/agent-model-sync.mjs" >&2
|
|
136
|
+
fi
|
|
137
|
+
|
|
131
138
|
echo "fable rung: $CUR -> $WANT"
|
|
132
139
|
echo "cost pricing: $PRICING -> $WANT_PRICING"
|
|
133
140
|
echo ""
|
|
@@ -46,7 +46,7 @@ behind.
|
|
|
46
46
|
### Full list
|
|
47
47
|
|
|
48
48
|
1. **One feature per run.** Every Figma URL, Confluence page, Jira ID, and Standards source the user supplies belongs to the **same feature**. The command never asks "which feature is this for?" or "which URL is primary?". Mixed inputs covering multiple features are treated as user error: surface the conflict, stop, and ask the user to split into separate runs.
|
|
49
|
-
2. **Section omission rule.** Sections with zero evidence are dropped entirely; no `TBD` placeholder section. **A rendered section keeps its canonical template number**, so the set has gaps and that is correct: `1, 2, 4, 9, 13, 14, 21` is a valid rendered document.
|
|
49
|
+
2. **Section omission rule.** Sections with zero evidence are dropped entirely; no `TBD` placeholder section. **A rendered section keeps its canonical template number**, so the set has gaps and that is correct: `1, 2, 4, 9, 13, 14, 21` is a valid rendered document. Numbers are never rewritten into a sequential `1..N`, because Locked 30 threads ids across sections BY NUMBER (`Section 15.1 scenario`, `Section 4.4`, `Section 3/5 layout cells`) and renumbering would send every one of those cross-references to the wrong section or to nothing. What IS checked: every rendered number is a real template number, and the numbers ascend without repeating.
|
|
50
50
|
3. **Citation discipline.** Every quoted UI string, endpoint path, error code, or analytics event name in Sections 2-4 must carry an inline citation: `[Figma annotation <nodeId>]` for copy taken from a Dev Mode annotation, `[Figma <nodeId>]` (MCP) or `[figma-export: <project-slug>/<screen-slug>:<nodeId>]` (local source) for design strings; `file:line` for repo evidence; `Confluence:<pageId>:<heading-slug>` for spec text. **Annotation-as-copy precedence:** when the project's `figma-config` has `annotations.enabled` and a node carries a Dev Mode annotation, that annotation is the authoritative copy for the node and the visible text layer is treated as a placeholder; cite the annotation, not the layer text. Never invent copy - blank beats a guess; a node whose annotation has the base language but is missing a target language emits a Section 20 (Risks) row rather than a fabricated value. Uncited quotes are downgraded to `[label TBD - see Open Questions]` and a row is added to Section 7 Risks. Subagent prose and Code Connect snippets are not citations.
|
|
51
51
|
4. **The spec is forward-looking.** Section bodies describe the new feature as drawn / specified. Findings that exist only in legacy code or the existing branch appear as `> Legacy reference: <text> (file:line)` blockquotes inside the relevant section, never as the lead sentence or a primary table row. A legacy-only finding with no forward counterpart goes to Section 7 Risks as a decision item: "current code does X; should the new feature keep, change, or drop this?".
|
|
52
52
|
5. **Output default = Local file.** The Phase 3.5 output picker keeps `Local file` pre-selected. Confluence and Jira outputs are never default-selected (see `analysis-output-confluence-on-request` memory).
|
|
@@ -56,13 +56,13 @@ behind.
|
|
|
56
56
|
9. **Per-platform output split.** One markdown file is produced per selected platform under `analysis/<feature>-<platform>.md`. There is no merged single file. Section duplication follows the A3 hybrid table in `$HOME/.claude/multi-agent-refs/analysis-template.md` (Sections 1 + 4 duplicated verbatim; Sections 2, 3, 5, 6, 7 projected per platform). Each file carries a YAML front-matter header naming the feature, platform, generated-at timestamp, sibling file list, and binding standards source.
|
|
57
57
|
10. **Output destination is asked after drafts exist.** Phase 3 renders each per-platform markdown to `/tmp/analysis-<feature-slug>-<timestamp>/` first. Only then does Phase 3.5 surface the output-destination picker (Local / Confluence / Jira). Drafts in `/tmp/` are the source of truth on resume; `/multi-agent:resume` re-uses them when `phase == awaiting_output_decision`.
|
|
58
58
|
11. **Repo-evidence reuse-first.** Phase 1b runs a repo-evidence collector against each selected repo, producing 13 buckets (services, dtos, useCases, validationRules, domainEntities, routes, coordinators, diConfigurators, uiComponents, tokens, localizationKeys, testingIdentifiers, analyticsEvents) with each row tagged `direct-match | same-domain | cross-cutting`. Section 7 emits `Reuse existing X (file:line)` rows for `direct-match` items and advisory rows for `cross-cutting` items. New-write tasks for items that have a `direct-match` are downgraded to Risks ("existing X candidate found; reuse or document why a new one is needed").
|
|
59
|
-
12. **MUST: Figma access - 3-tier fallback chain, pipeline-wide.** When any Figma URL or node ID is supplied (Phase 0 Step 5), Phase 1 establishes a Figma ground-truth artefact via the 3-tier chain before any UI synthesis runs: (1) Figma MCP `get_design_context` / `get_screenshot` / `get_metadata` for every referenced frame, with one re-auth retry on auth failure; (2) Figma REST API (`GET /v1/files/{fileKey}/nodes`, `GET /v1/images/{fileKey}`) using the PAT resolved through `~/.claude/lib/credential-store.sh get <logical-key>` where `<logical-key>` = `prefs.global.keychainMapping.figma`; (3) a user-attached screenshot as last resort. The tier in use is persisted as `state.figmaAccess.tier`. On Tier 1 the `CodeConnectSnippet` blocks name the exact target component consumed verbatim in Section 2 and Section 7. On Tier 2 the canonical-component decision falls back to the repo's `*.figma.swift` / `*.figma.kt` mappings keyed by `fileKey` + `nodeId`. On Tier 3 the canonical-component decision becomes "tier-3 best-fit pending design review" and produces a forced Open Question in Section 7 plus a Phase 4 `review_blocking` flag. Sound-alike alternatives, "more flexible" wrappers, and extrapolating from Confluence text-only "Component Kompozisyonu" / "Component Inventory" tables are forbidden in every tier. If all three tiers fail, halt the run and ask the user for access; never proceed with text-derived guesses. Section 7 architecture decisions must cite the node ID (Tier 1 or Tier 2) or the user-screenshot reference (Tier 3) for every UI atom row. Violations cost rebuild rounds (raw-primitive substitution; sound-alike-component swap). Generic rule rationale and checklist: see `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma access - 3-tier fallback chain (BLOCKING, pipeline-wide)". Memory: `[[figma-no-guesswork]]`. **A channel's design may not be called missing until the file tree has been scanned.** Run `figma-screenshot.sh --discover-sections --file-key <k> --feature-slug <s>`; sibling sections it returns join `evidence.figma[]` as their own entries and are never re-asked (Locked 1). Only an empty `candidates` list lets a channel be recorded as having no design, and the record must cite the scan (`fileKey`, `pagesScanned`, `tokensTried`). Declaring a channel missing without scanning fails the dispatch gate
|
|
59
|
+
12. **MUST: Figma access - 3-tier fallback chain, pipeline-wide.** When any Figma URL or node ID is supplied (Phase 0 Step 5), Phase 1 establishes a Figma ground-truth artefact via the 3-tier chain before any UI synthesis runs: (1) Figma MCP `get_design_context` / `get_screenshot` / `get_metadata` for every referenced frame, with one re-auth retry on auth failure; (2) Figma REST API (`GET /v1/files/{fileKey}/nodes`, `GET /v1/images/{fileKey}`) using the PAT resolved through `~/.claude/lib/credential-store.sh get <logical-key>` where `<logical-key>` = `prefs.global.keychainMapping.figma`; (3) a user-attached screenshot as last resort. The tier in use is persisted as `state.figmaAccess.tier`. On Tier 1 the `CodeConnectSnippet` blocks name the exact target component consumed verbatim in Section 2 and Section 7. On Tier 2 the canonical-component decision falls back to the repo's `*.figma.swift` / `*.figma.kt` mappings keyed by `fileKey` + `nodeId`. On Tier 3 the canonical-component decision becomes "tier-3 best-fit pending design review" and produces a forced Open Question in Section 7 plus a Phase 4 `review_blocking` flag. Sound-alike alternatives, "more flexible" wrappers, and extrapolating from Confluence text-only "Component Kompozisyonu" / "Component Inventory" tables are forbidden in every tier. If all three tiers fail, halt the run and ask the user for access; never proceed with text-derived guesses. Section 7 architecture decisions must cite the node ID (Tier 1 or Tier 2) or the user-screenshot reference (Tier 3) for every UI atom row. Violations cost rebuild rounds (raw-primitive substitution; sound-alike-component swap). Generic rule rationale and checklist: see `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma access - 3-tier fallback chain (BLOCKING, pipeline-wide)". Memory: `[[figma-no-guesswork]]`. **A channel's design may not be called missing until the file tree has been scanned.** Run `figma-screenshot.sh --discover-sections --file-key <k> --feature-slug <s>`; sibling sections it returns join `evidence.figma[]` as their own entries and are never re-asked (Locked 1). Only an empty `candidates` list lets a channel be recorded as having no design, and the record must cite the scan (`fileKey`, `pagesScanned`, `tokensTried`). Declaring a channel missing without scanning fails the dispatch gate, because a channel's frames often sit in a sibling section of the same file.
|
|
60
60
|
13. **Gherkin user stories.** Section 4 scenarios use Given / When / Then. Plain-prose user stories are rejected at render time.
|
|
61
61
|
14. **Goals and Non-Goals are paired.** Section 2 always carries both columns. A row in only the goal column without an explicit non-goal counterpart is rejected. Vague "out of scope" phrasing does not satisfy the non-goal column; each non-goal names what is excluded.
|
|
62
62
|
15. **New assets default to SVG.** Section 8 entries marked `new` are SVG unless a documented exception is captured in the rationale column (Lottie for motion design, optimized PNG for raster-only icons). PDF, JPG, and unoptimized PNG are rejected.
|
|
63
63
|
16. **Files-to-Add tag mandatory.** Every row in Section 14 carries one tag from `Reuse | Add new | Modify`. Untagged rows fail the dispatch gate.
|
|
64
64
|
17. **API response variants exhaustive.** Section 9 lists every HTTP status code returned by the endpoint with at least one example body and the matching UI outcome. Phrases like "other errors" or "various 4xx" are rejected.
|
|
65
|
-
18. **Screenshots embedded, not linked, and every inventory row gets one.** Section 5 frame galleries reference local PNG files. Dispatch uploads them as Confluence multipart attachments and injects `<ac:image><ri:attachment ri:filename="..." /></ac:image>` into the page body. The gallery is generated from the inventory, never hand-picked: one row, one embedded image, ordered by node id and titled `<nodeId> <frame name>`. A bare filename in a table cell is text to the reader, not a picture
|
|
65
|
+
18. **Screenshots embedded, not linked, and every inventory row gets one.** Section 5 frame galleries reference local PNG files. Dispatch uploads them as Confluence multipart attachments and injects `<ac:image><ri:attachment ri:filename="..." /></ac:image>` into the page body. The gallery is generated from the inventory, never hand-picked: one row, one embedded image, ordered by node id and titled `<nodeId> <frame name>`. A bare filename in a table cell is text to the reader, not a picture: an attachment that is uploaded but not embedded is invisible on the page. Narrowing to a canonical subset for page weight is not a call the run makes on its own; the display-width cap (`md2confluence-v3.py --image-width`, phone frames overriding to 320) already solves readability without dropping evidence. Dispatch reports the embedded-over-inventory ratio and warns below 1. URL-only Figma references in Section 5 are a review finding, not a validator ERROR: the check belongs to the Confluence dispatch step, which is the only place that knows whether an attachment upload succeeded.
|
|
66
66
|
19. **All Figma variants drilled, and drilled means read.** When a Figma section URL is supplied, all child frames are drilled, not just the canonical default. The renderer enumerates child frames via `mcp__claude_ai_Figma__get_metadata` (Tier 1) or `figma-screenshot.sh --section` (Tier 2) and produces one Section 5.1 row per child frame. Counting them is not drilling them: extract the visible `TEXT` layers inside each frame's own bounds, dropping hidden layers and component-instance boilerplate that overflows the frame (default card and widget filler can outnumber the real text several times over). A frame may be called out of scope only by quoting its own extracted text; "the name looked unrelated" is not a reason. Opening a Section 20 question about a frame requires that its text was extracted first, and asking about a frame nobody read fails the dispatch gate: a frame whose name looks ambiguous can spell out a whole step in its text.
|
|
67
67
|
20. **Localization mode (ownership-aware).** Section 10's shape is driven by the project `figma-config` `localization.ownership` (default `in-repo`). The locale set comes from `localization.locales` (default `tr, en, ar, de, es, fr, it, ru`); do not hardcode a locale list in the render.
|
|
68
68
|
- **`in-repo`** (default, historical behavior): new keys carry filled cells for every configured locale. Placeholder values `[bekleniyor: çeviri ekibi]` / `[pending: translation team]` are a soft state and the dispatch report flags `i18n_pending: <count>` as a blocker. Empty cells are rejected outright.
|
|
@@ -82,5 +82,5 @@ behind.
|
|
|
82
82
|
32. **Corporate backbone always renders.** In the `corporate` profile the Locked 2 omission rule is replaced for Part A and the footer: those sections render even with zero evidence, carrying `N/A` when the section is genuinely out of scope for the feature and `EKLENECEK` when evidence is expected but missing. This is the point of a requirements document - a reader has to be able to tell "we considered hardware needs and there are none" from "nobody looked". Every `EKLENECEK` emits a matching Section 20 Risks and Open Questions row naming what is missing and who can answer it; an `EKLENECEK` with no such row fails the dispatch gate, because an unanswered question nobody owns is how a placeholder reaches production. **Missing inputs never block the run**: the corporate source practice of halting until every input arrives is deliberately not adopted - the document is produced with `EKLENECEK` in the gaps and the gaps are raised in Section 20. Part B follows the global omission table unchanged. In the `global` profile Locked 2 applies as written, with no placeholder of any kind.
|
|
83
83
|
33. **References are built from the evidence record, not written.** Section 21 is emitted by `$HOME/.claude/scripts/build-references.mjs` from `state.analysisSpec.evidence.*` in both profiles. Each row carries a precision anchor in its `Sürüm / Ref` column - Figma node id, Confluence `pageId` plus page version, the commit SHA a repo was read at, the Swagger spec version - because a reference with no anchor points at a moving target. Each row carries an `Erişim / Access` cell: a declared source that could not be fetched still gets a row reading `erişilemedi (<reason>)`, since a silently dropped source reads to the next person as a source that never existed. User statements from the conversation that no fetched source contains are recorded as `Serbest metin` rows, quoted verbatim, with the decision they settled. **Coverage gate**: every entry in `evidence.figma[]`, `confluence[]`, `jira[]`, `swagger[]`, `repo[]`, `standards[]`, `firebase[]`, `documents[]`, `outside[]`, `freeText[]` and every entry in `evidence.fetchErrors[]` must appear as a row, and every row must map to an evidence entry. A source that shaped the document but is missing from References fails the dispatch gate; so does an invented row with no evidence behind it.
|
|
84
84
|
34. **Stack-optional render.** Platform and repo selection are optional. When `state.analysisSpec.platforms[]` is empty, the run still completes: the analysis layers that do not need a target repository render in full - Part A and Part B in the corporate profile, Sections 1-12 and 16-17 in the global profile - and only the development layer is dropped (corporate Part C; global Sections 13, 14, 15) along with the Pass B projection, since there are no conventions to project onto. A Section 20 row records that the development analysis awaits a repo selection. **The channel split survives the missing repo.** Channels are derived from the evidence instead of repo stack tags (`intake.md` Step 3 carries the signal table) and one document is emitted per derived channel - `mobile`, `web`, or both. A phone screen and a browser screen carry different requirements before anyone has picked a repository; the split (Locked 9) exists to carry that difference and only its *projection* half needs conventions. `mobile` stays one channel rather than iOS plus Android, since without conventions nothing tells the two apart. Evidence with no interface at all yields a single channel-agnostic `<feature>.md`. Files land under `~/Desktop/multiAgentAnalysis/<feature-name>/`, named `<feature>-<channel>.md` (or `<feature>.md` for the channel-agnostic case): the repo-relative `analysis/` path has nothing to be relative to without a repo, and the current working directory is never used, since for a repo-less run it is arbitrary. Desktop rather than a hidden directory because the document is a deliverable somebody is meant to open and hand over, and `multiAgentAnalysis` rather than a bare `Analysis` because a generic word collides with whatever else is on a desktop while the producer name groups every run this command ever writes. The Phase 3.5 picker shows the resolved path and takes an override through its Other input. A requirements document is useful before anyone has decided which repository will hold the code, and refusing to produce one until that decision exists inverts the order the work actually happens in.
|
|
85
|
-
35. **The document is reviewed before it is published.**
|
|
85
|
+
35. **The document is reviewed before it is published.** A deterministic validator checks structure, not content: a channel nobody searched for, an open question about a frame nobody opened, and an unowned `EKLENECEK` marker all pass it, and every one is what a reader catches on the first pass. Phase 3.2 runs `phases/phase-3-review.md` Step 0 (strict validator, the host's three-reviewer set, triage) on the draft before the destination is chosen: a finding is cheap while nothing is written. Reviewers are subagents holding `analysis/review.md`, never the context that wrote the document, which cannot notice a search it never thought to run. A blocking finding returns to Phase 2b with dispatch closed and never becomes an open question, since "the document is wrong" is not something to ask the reader; capped at two returns. Phase 3.3 sorts every remaining gap into searched-and-closed, asked-and-answered, or `AS-NN`; an unstamped gap fails the dispatch gate. Autopilot runs both; only the asking degrades, into rows stamped `autopilot: could not ask`.
|
|
86
86
|
36. **A redesign records v1 before it plans v2.** `options.redesign` is an opt-in on the `options.uiTests` axis, never a third `mode` value: `mode` says how many sections, `redesign` says which content, and a `mode: redesign` would switch off the Full-mode traceability, Test Plan and rule-to-test gates in exactly the documents that need them. It adds three sub-sections and no top-level section: current behaviour with `CB-<slug>-NN` ids and `repo/file:line` citations, the v1 to v2 endpoint mapping, and a difference list over a closed status vocabulary. Every `Missing` and `Partial` owes a Section 20 row by `AS-NN`, and a `CB-` id in one table but not the other fails in both directions - a behaviour that is in the code and on nobody's difference list is what a redesign loses and production finds. Evidence and certainty are derived from `repoEvidence`, never graded by the writer (Locked 24, same reason), and `options.redesign` is an `evidence_digest` input. Contract and the eight checks: `analysis/redesign.md`, loaded only on a redesign run.
|
|
@@ -41,7 +41,9 @@ intake opt-in, written into the front-matter, enforced by the validator's
|
|
|
41
41
|
## The three artefacts
|
|
42
42
|
|
|
43
43
|
Section numbers are per profile. The global profile uses sub-sections of existing
|
|
44
|
-
sections (4.5, 4.6, 9.5) and the corporate profile
|
|
44
|
+
sections (4.5, 4.6, 9.5) and the corporate profile nests them one level deeper in
|
|
45
|
+
its own spine (2.1.1 current behaviour, 2.3.1 difference list, 5.(N+4).1 endpoint
|
|
46
|
+
mapping); the validator looks each one up by profile. No
|
|
45
47
|
new top-level section is introduced in either, because section numbers are quoted
|
|
46
48
|
in 172 places and renumbering them to add a mode is a worse trade than nesting.
|
|
47
49
|
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
1b. **Template selection** (Locked 31): read `state.analysisSpec.profile`. `global` renders against `$HOME/.claude/multi-agent-refs/analysis-template.md`; `corporate` renders against `$HOME/.claude/multi-agent-refs/analysis-template-corporate.md`. The evidence is the same either way - this step chooses the projection, nothing else. In the corporate profile the Part A backbone and the footer render even with zero evidence, carrying `N/A` or `EKLENECEK`, and each `EKLENECEK` emits its Section 20 row (Locked 32).
|
|
10
10
|
|
|
11
|
-
2. **Markdown render**: for each platform in `state.analysisSpec.platforms[]`, concatenate the per-platform spec into one markdown file. Tables in pipe-syntax.
|
|
11
|
+
2. **Markdown render**: for each platform in `state.analysisSpec.platforms[]`, concatenate the per-platform spec into one markdown file. Tables in pipe-syntax. Each rendered section keeps its canonical number, so omitted sections leave gaps (Locked 2). Corporate Part A and footer never drop; Parts B and C follow the omission table.
|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**Missing inputs panel** above Section 1: `build-references.mjs "$STATE" --lang "$LANG" --missing-panel` (prints nothing when every source was read). "Not reachable" is never written as "does not exist".
|
|
14
|
+
|
|
15
|
+
**When `platforms[]` is empty** (Locked 34), the loop runs once per channel derived from the evidence (`mobile`, `web`, or one channel-agnostic pass). The development layer (corporate Part C, global Sections 13-15) and the Pass B projection are skipped, Section 20 records that they await a repo selection, and front-matter `platform` reads `none`. Everything else renders in full.
|
|
14
16
|
|
|
15
17
|
3. **Humanizer pass (required: actually invoke the `ai-common-toolkit:humanizer` skill on the rendered markdown - the punctuation grep alone does NOT satisfy this step)** (`technical-explanatory` tone for the scratch buffer; per-channel re-humanize happens in Phase 4 when actually emitting):
|
|
16
18
|
```
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
content: <markdown>
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
**Explicit punctuation policy** (enforced by `stripFancyPunctuation: true`): no em-dash (U+2014), no en-dash (U+2013), no horizontal ellipsis (U+2026), no curly quotes (U+2018, U+2019, U+201C, U+201D), no section sign (U+00A7). The humanizer replaces these with ASCII equivalents (`-`, `:`, `,`, `...`, `'`, `"`, and `bölüm` / `section` for the section sign per `outputLanguage`) before emit. Tables, code blocks, URLs, and front-matter YAML are exempt.
|
|
26
|
+
**Explicit punctuation policy** (enforced by `stripFancyPunctuation: true`): no em-dash (U+2014), no en-dash (U+2013), no horizontal ellipsis (U+2026), no curly quotes (U+2018, U+2019, U+201C, U+201D), no section sign (U+00A7). The humanizer replaces these with ASCII equivalents (`-`, `:`, `,`, `...`, `'`, `"`, and `bölüm` / `section` for the section sign per `outputLanguage`) before emit. Tables, code blocks, URLs, and front-matter YAML are exempt. Verify with `node $HOME/.claude/scripts/validate-analysis-doc.mjs <file>`, not a Perl-regex grep (BSD grep lacks it, so "zero matches" is trivially true). Readability rules live in the humanizer skill; this paragraph owns only the punctuation policy. **Diacritics are PRESERVED: only the listed codepoints are replaced. Turkish letters (ş, ç, ğ, ı, İ, ö, ü) stay verbatim; never ASCII-fold (`Geliştirme Özeti`, not `Gelistirme Ozeti`). ASCII-folded Turkish fails review.**
|
|
25
27
|
|
|
26
28
|
3b. **Build Section 21 References** (Locked 33): emit the table with
|
|
27
29
|
|
|
@@ -207,36 +209,33 @@ Provenance:
|
|
|
207
209
|
|
|
208
210
|
## Confluence write (on-request only)
|
|
209
211
|
|
|
210
|
-
|
|
212
|
+
Confluence is NEVER default-selected at Phase 3.5. Only when the user explicitly asks to post does the command:
|
|
211
213
|
|
|
212
214
|
1. Resolve the token via `~/.claude/lib/credential-store.sh get <key>`, where `<key>` is read from `prefs.global.keychainMapping.confluence` (per-user mapping; never hardcode the service name in this doc - see channel adapter doc `$HOME/.claude/multi-agent-refs/channels/confluence.md` for the lookup contract).
|
|
213
215
|
2. Use parent page URL from user input. No default parent is hardcoded here; the user picks one at the prompt (LRU recents come from `prefs.projects[<project>].confluenceUrls`).
|
|
214
216
|
|
|
215
|
-
**Corporate profile exception.**
|
|
217
|
+
**Corporate profile exception.** With the bindings configured, the prompt is skipped: space `prefs.global.analysisProfile.corporate.confluenceSpaceKey`, parent `prefs.global.analysisProfile.corporate.confluenceParentPageId`, title from `prefs.global.analysisProfile.corporate.titleFormat` with `prefs.global.analysisProfile.corporate.titlePrefix` filling `{prefix}`. Any of the four missing falls back to the prompt; a colliding title becomes an update (PUT with version bump), never a second page.
|
|
216
218
|
3. **Publish with `md2confluence-v3.py`, never a hand-rolled conversion + curl.** It
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
gallery render as pictures rather than filenames (Locked 18). It also warns when a
|
|
221
|
-
body references an image no attachment matched, which is how a missing screenshot
|
|
222
|
-
announces itself.
|
|
219
|
+
does the storage-XML conversion (`channels/confluence.md`), uploads every PNG in
|
|
220
|
+
`--attachments-dir`, injects `<ac:image><ri:attachment/>` so a frame gallery renders
|
|
221
|
+
as pictures (Locked 18), and warns on an image reference no attachment matched.
|
|
223
222
|
|
|
224
223
|
```bash
|
|
224
|
+
CONFLUENCE_BASE_URL="https://$(jq -r '.global.hosts.confluence // empty' "$HOME/.claude/multi-agent-preferences.json")"
|
|
225
|
+
export CONFLUENCE_BASE_URL
|
|
225
226
|
python3 "$HOME/.claude/lib/md2confluence-v3.py" create \
|
|
226
227
|
--space "$SPACE" --parent-page-id "$PARENT" --title "$TITLE" \
|
|
227
228
|
--markdown "$DRAFT" --attachments-dir "$(dirname "$DRAFT")"
|
|
228
229
|
# updating an existing page: `update --page-id <ID>` instead of `create --space ...`
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
Screenshots must already sit in `--attachments-dir
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
warning list means the page shipped with missing pictures; surface it rather than
|
|
240
|
-
reporting a clean publish. Surface the page URL in the Phase 4 report.
|
|
232
|
+
Screenshots must already sit in `--attachments-dir`: point `figma-screenshot.sh
|
|
233
|
+
--output-dir` at the draft directory during evidence collection, or the gallery
|
|
234
|
+
renders filenames. MCP asset URLs expire after 7 days; cells reference the local file.
|
|
235
|
+
**Draft title:** while `status` is not `final`, prefix `[TASLAK] ` / `[DRAFT] ` (or fill the corporate `titleFormat` `{status}`); the final publish drops it.
|
|
236
|
+
4. Read the script's JSON envelope (page id and URL, attachments, `image reference has
|
|
237
|
+
no matching attachment` warnings). A warning means missing pictures: surface it, not a
|
|
238
|
+
clean publish. Surface the page URL in the Phase 4 report.
|
|
239
|
+
5. **Read the page back:** `confluence-readback.mjs --page-id "$PAGE_ID" --markdown "$DRAFT"`. Exit 1 names missing headings and unattached images; report it, never republish automatically.
|
|
241
240
|
|
|
242
241
|
Token miss handling: surface a single line `WARN: Confluence token not configured (prefs.global.keychainMapping.confluence). Run /multi-agent:setup or skip Confluence output.` and continue with local-only output.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
>
|
|
5
5
|
> The same three source labels drive `research/engine.md`, which generalises this walk to any gap list (maturity blockers, open questions) and adds a deterministic check of every cited source (`research-gate.mjs`) for runs nobody watches.
|
|
6
6
|
>
|
|
7
|
-
> The
|
|
7
|
+
> The 36 inherited decisions are in `locked.md`; the ones below are this engine's own. Core invariant: the document is authoritative and forward-looking. The engine never invents an answer - when no source produces a credible candidate the only options are Defer and Other.
|
|
8
8
|
|
|
9
9
|
## New Locked decisions (this command)
|
|
10
10
|
|