raptor-aios 0.7.0 → 0.8.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 +26 -0
- package/README.md +2 -0
- package/dist/_core/dist/jira/dialects.js +2 -1
- package/dist/_core/dist/jira/index.js +1 -1
- package/dist/_core/dist/jira/mapper.js +121 -7
- package/dist/_core/dist/presets/index.js +1 -1
- package/dist/_core/dist/presets/registry.js +12 -1
- package/dist/_core/package.json +1 -1
- package/dist/_core/templates/spec.md.hbs +9 -5
- package/dist/commands/init.js +9 -8
- package/dist/commands/jira/pull.js +6 -1
- package/dist/commands/new.js +7 -2
- package/dist/shared/jira.js +4 -0
- package/package.json +1 -1
- package/scripts/prepare-npm.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
+
## [0.8.0] - 2026-06-09
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`raptor new --jira` now seeds the spec from the WHOLE card, not just its description.** A ticket's scenarios, requirements, acceptance criteria, test caveats and design/documentation links — wherever they live — are captured, kept verbatim, and routed into the spec for the agent to redistribute (enforced by `gate.spec.ready`, not a brittle extractor). Four parts:
|
|
11
|
+
- **Fetch-all** — the `mcp-atlassian` dialect requests `fields="*all"` with `expand="names,renderedFields"` instead of six fixed fields, so custom fields and the id→label map come back.
|
|
12
|
+
- **Link preservation** — `flattenAdf` now surfaces a link mark's `href` (as `[text](url)`) and smart-link card URLs (`inlineCard`/`blockCard`/`embedCard`), so a Figma link embedded in the body survives flattening and re-enables the `design/` scaffold + `gate.design.ready`.
|
|
13
|
+
- **Rich custom fields** — `JiraIssue` gains `customFields[]` (id, human label, value, canonical bucket). A PT/EN classifier (`classifyField`) routes "Critérios de aceite", "Cenários", "Requisitos", "Fora do escopo", "Documentação"… into the right section; `flattenFieldValue` tolerates ADF/select/multi-select shapes. Nothing is dropped — an unrecognised field falls to an appendix.
|
|
14
|
+
- **Verbatim ticket body** — `mapIssueToSpecContext` builds `jiraTicketBody` (description + labelled `### <field>` sections), folds acceptance-bucket fields into the AC list (de-duped, feeding `acceptance.ids`/M7), and the `spec.md` Problem Statement dumps it with explicit redistribution instructions. `raptor jira pull` and the clarify-time `jira-refresh.md` surface the same fields.
|
|
15
|
+
- **New `jira.custom_fields` config** — deterministic `id`/`label → bucket` overrides for servers that don't honour `expand=names`.
|
|
16
|
+
- **New docs** — `docs/jira-spec-enrichment.md` documents how the card is captured, how the agent is instructed, and how the gates validate it (anchored on a real card).
|
|
17
|
+
|
|
18
|
+
### Internal
|
|
19
|
+
|
|
20
|
+
- New `extractCustomFields` / `classifyField` / `flattenFieldValue` and `MapIssueOptions` in `jira/mapper.ts`; `JiraCustomField` / `JiraFieldBucket` types. A test proves `gate.spec.ready` blocks an approved-but-un-redistributed Jira seed on three independent axes (leftover `[PREENCHER]`, uncovered user stories, unmirrored `acceptance.ids`) — locking in "Raptor delivers verbatim, the agent redistributes, the gate enforces". Verified end-to-end against the real KAN-2 card.
|
|
21
|
+
|
|
22
|
+
## [0.7.1] - 2026-06-08
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **`init --preset mobile` no longer silently disables the M1–M6 gates.** The registry only knew the canonical id `mobile-opinionated`, so passing the natural shorthand `mobile` persisted a raw `preset: mobile` that `getPreset` could not resolve — and `verify` ran **none** of the mobile preset gates without any error. Now a `mobile` → `mobile-opinionated` alias resolves both ways: `init` persists the **canonical** id in `raptor.yml`, `raptor.manifest.json` and `init-options.json`, and any existing project that already stored `preset: mobile` resolves correctly at verify time.
|
|
27
|
+
|
|
28
|
+
### Internal
|
|
29
|
+
|
|
30
|
+
- `registry.ts` gains an alias map and a `resolvePresetId()` helper (also exported); `getPreset` resolves aliases and tolerates `undefined`/empty input. `init` persists `resolvePresetId(flags.preset) ?? flags.preset` instead of the raw flag.
|
|
31
|
+
|
|
6
32
|
## [0.7.0] - 2026-06-08
|
|
7
33
|
|
|
8
34
|
### Added
|
package/README.md
CHANGED
|
@@ -471,6 +471,8 @@ raptor jira pull APP-1234 # importa a issue
|
|
|
471
471
|
raptor new login --jira APP-1234 # semeia a spec a partir da issue
|
|
472
472
|
```
|
|
473
473
|
|
|
474
|
+
> 📖 Como o card é capturado **por inteiro** (campos ricos, links de Figma) e redistribuído na spec — com os gates cobrando: veja [docs/jira-spec-enrichment.md](docs/jira-spec-enrichment.md).
|
|
475
|
+
|
|
474
476
|
---
|
|
475
477
|
|
|
476
478
|
## 📖 Referência de comandos CLI
|
|
@@ -52,7 +52,8 @@ export const MCP_ATLASSIAN_DIALECT = {
|
|
|
52
52
|
args: {
|
|
53
53
|
getIssue: (_cloudId, key) => ({
|
|
54
54
|
issue_key: key,
|
|
55
|
-
fields: "
|
|
55
|
+
fields: "*all",
|
|
56
|
+
expand: "names,renderedFields",
|
|
56
57
|
comment_limit: 10,
|
|
57
58
|
}),
|
|
58
59
|
search: (_cloudId, jql, max) => ({ jql, limit: max }),
|
|
@@ -3,4 +3,4 @@ export * from "./credentials.js";
|
|
|
3
3
|
export { AtlassianOAuthProvider, isAccessTokenExpired, openBrowser, startCallbackServer, } from "./oauth.js";
|
|
4
4
|
export { connectJira, connectMcpServer, makeJiraClient, clientToToolCaller, decodeToolResult, unwrapEnvelope, expandEnv, } from "./mcp-client.js";
|
|
5
5
|
export { ROVO_DIALECT, MCP_ATLASSIAN_DIALECT, DIALECTS, resolveDialect, } from "./dialects.js";
|
|
6
|
-
export { parseJiraIssue, parseCreatedIssue, extractComments, mapIssueToSpecContext, flattenAdf, extractAcceptanceCriteria, } from "./mapper.js";
|
|
6
|
+
export { parseJiraIssue, parseCreatedIssue, extractComments, extractCustomFields, classifyField, flattenFieldValue, mapIssueToSpecContext, flattenAdf, extractAcceptanceCriteria, } from "./mapper.js";
|
|
@@ -17,6 +17,12 @@ export function parseJiraIssue(raw, baseUrl) {
|
|
|
17
17
|
nestedName(fields["issue_type"]);
|
|
18
18
|
const labels = strArray(fields["labels"]);
|
|
19
19
|
const url = resolveUrl(issue, fields, key, baseUrl);
|
|
20
|
+
const names = isRecord(issue["names"])
|
|
21
|
+
? issue["names"]
|
|
22
|
+
: isRecord(obj["names"])
|
|
23
|
+
? obj["names"]
|
|
24
|
+
: {};
|
|
25
|
+
const customFields = extractCustomFields(fields, names);
|
|
20
26
|
return {
|
|
21
27
|
key,
|
|
22
28
|
summary,
|
|
@@ -26,9 +32,57 @@ export function parseJiraIssue(raw, baseUrl) {
|
|
|
26
32
|
labels,
|
|
27
33
|
acceptanceCriteria: extractAcceptanceCriteria(fields, description),
|
|
28
34
|
comments: extractComments(fields),
|
|
35
|
+
customFields,
|
|
29
36
|
...(url ? { url } : {}),
|
|
30
37
|
};
|
|
31
38
|
}
|
|
39
|
+
export function extractCustomFields(fields, names) {
|
|
40
|
+
const out = [];
|
|
41
|
+
for (const [id, raw] of Object.entries(fields)) {
|
|
42
|
+
if (!/^customfield_/i.test(id))
|
|
43
|
+
continue;
|
|
44
|
+
const value = flattenFieldValue(raw);
|
|
45
|
+
if (!value)
|
|
46
|
+
continue;
|
|
47
|
+
const name = str(names[id]) || id;
|
|
48
|
+
out.push({ id, name, value, bucket: classifyField(name) });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
export function classifyField(label) {
|
|
53
|
+
const l = label.toLowerCase();
|
|
54
|
+
if (/aceite|aceita[çc][ãa]o|acceptance/.test(l))
|
|
55
|
+
return "acceptance";
|
|
56
|
+
if (/cen[áa]rio|scenario/.test(l))
|
|
57
|
+
return "scenarios";
|
|
58
|
+
if (/n[ãa]o[-\s]?funcional|non[-\s]?functional|\brnf\b/.test(l))
|
|
59
|
+
return "nonFunctional";
|
|
60
|
+
if (/requisito|requirement|\brf\b/.test(l))
|
|
61
|
+
return "functional";
|
|
62
|
+
if (/fora do escopo|out of scope|n[ãa]o escopo/.test(l))
|
|
63
|
+
return "outOfScope";
|
|
64
|
+
if (/documenta|documentation|link|refer[êe]ncia|design|figma/.test(l))
|
|
65
|
+
return "docs";
|
|
66
|
+
return "other";
|
|
67
|
+
}
|
|
68
|
+
export function flattenFieldValue(raw) {
|
|
69
|
+
if (raw == null)
|
|
70
|
+
return "";
|
|
71
|
+
if (typeof raw === "string")
|
|
72
|
+
return raw.trim();
|
|
73
|
+
if (Array.isArray(raw)) {
|
|
74
|
+
return raw.map(flattenFieldValue).filter(Boolean).join("\n").trim();
|
|
75
|
+
}
|
|
76
|
+
if (isRecord(raw)) {
|
|
77
|
+
if ("content" in raw || raw["type"] === "doc")
|
|
78
|
+
return flattenAdf(raw).trim();
|
|
79
|
+
const opt = str(raw["value"]) || str(raw["name"]);
|
|
80
|
+
if (opt)
|
|
81
|
+
return opt;
|
|
82
|
+
return flattenAdf(raw).trim();
|
|
83
|
+
}
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
32
86
|
export function parseCreatedIssue(raw) {
|
|
33
87
|
const obj = coerceObject(raw);
|
|
34
88
|
if (!obj)
|
|
@@ -58,11 +112,38 @@ export function extractComments(fields) {
|
|
|
58
112
|
}
|
|
59
113
|
return out;
|
|
60
114
|
}
|
|
61
|
-
export function mapIssueToSpecContext(issue) {
|
|
62
|
-
const
|
|
115
|
+
export function mapIssueToSpecContext(issue, opts = {}) {
|
|
116
|
+
const overrides = opts.fieldBuckets ?? {};
|
|
117
|
+
const custom = (issue.customFields ?? []).map((cf) => {
|
|
118
|
+
const ov = overrides[cf.id] ?? overrides[cf.name.toLowerCase()];
|
|
119
|
+
return ov ? { ...cf, bucket: ov } : cf;
|
|
120
|
+
});
|
|
121
|
+
const ac = [];
|
|
122
|
+
const seen = new Set();
|
|
123
|
+
const pushAc = (line) => {
|
|
124
|
+
const t = line.trim();
|
|
125
|
+
if (t && !seen.has(t)) {
|
|
126
|
+
seen.add(t);
|
|
127
|
+
ac.push(t);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
issue.acceptanceCriteria.forEach(pushAc);
|
|
131
|
+
for (const cf of custom)
|
|
132
|
+
if (cf.bucket === "acceptance")
|
|
133
|
+
toLines(cf.value).forEach(pushAc);
|
|
134
|
+
const sections = custom
|
|
135
|
+
.filter((cf) => cf.bucket !== "acceptance")
|
|
136
|
+
.map((cf) => ({ title: cf.name, body: cf.value, bucket: cf.bucket }));
|
|
137
|
+
const ticketBody = [
|
|
138
|
+
issue.description,
|
|
139
|
+
...sections.map((s) => `### ${s.title}\n${s.body}`),
|
|
140
|
+
]
|
|
141
|
+
.filter((s) => s && s.trim())
|
|
142
|
+
.join("\n\n");
|
|
63
143
|
return {
|
|
64
144
|
jiraSummary: issue.summary,
|
|
65
145
|
jiraDescription: issue.description,
|
|
146
|
+
jiraTicketBody: ticketBody,
|
|
66
147
|
jiraStatus: issue.status ?? "",
|
|
67
148
|
jiraIssueType: issue.issueType ?? "",
|
|
68
149
|
jiraUrl: issue.url ?? "",
|
|
@@ -71,6 +152,7 @@ export function mapIssueToSpecContext(issue) {
|
|
|
71
152
|
jiraAcceptanceNumbered: ac.map((text, i) => ({ n: i + 1, text })),
|
|
72
153
|
jiraAcceptanceNextN: ac.length + 1,
|
|
73
154
|
jiraHasAcceptance: ac.length > 0,
|
|
155
|
+
jiraCustomSections: sections,
|
|
74
156
|
};
|
|
75
157
|
}
|
|
76
158
|
export function flattenAdf(node) {
|
|
@@ -82,8 +164,17 @@ export function flattenAdf(node) {
|
|
|
82
164
|
return node.map(flattenAdf).join("");
|
|
83
165
|
if (!isRecord(node))
|
|
84
166
|
return "";
|
|
85
|
-
if (typeof node["text"] === "string")
|
|
86
|
-
|
|
167
|
+
if (typeof node["text"] === "string") {
|
|
168
|
+
const text = node["text"];
|
|
169
|
+
const href = linkHref(node["marks"]);
|
|
170
|
+
return href && href !== text ? `[${text}](${href})` : text;
|
|
171
|
+
}
|
|
172
|
+
const type = str(node["type"]);
|
|
173
|
+
if (type === "inlineCard" || type === "blockCard" || type === "embedCard") {
|
|
174
|
+
const url = cardUrl(node["attrs"]);
|
|
175
|
+
if (url)
|
|
176
|
+
return type === "inlineCard" ? url : `${url}\n`;
|
|
177
|
+
}
|
|
87
178
|
const inner = flattenAdf(node["content"]);
|
|
88
179
|
const blockTypes = new Set([
|
|
89
180
|
"paragraph",
|
|
@@ -94,7 +185,6 @@ export function flattenAdf(node) {
|
|
|
94
185
|
"codeBlock",
|
|
95
186
|
"blockquote",
|
|
96
187
|
]);
|
|
97
|
-
const type = str(node["type"]);
|
|
98
188
|
if (type === "hardBreak")
|
|
99
189
|
return "\n";
|
|
100
190
|
if (type === "listItem")
|
|
@@ -103,16 +193,40 @@ export function flattenAdf(node) {
|
|
|
103
193
|
return `${inner}\n`;
|
|
104
194
|
return inner;
|
|
105
195
|
}
|
|
196
|
+
function linkHref(marks) {
|
|
197
|
+
if (!Array.isArray(marks))
|
|
198
|
+
return undefined;
|
|
199
|
+
for (const m of marks) {
|
|
200
|
+
if (isRecord(m) && m["type"] === "link" && isRecord(m["attrs"])) {
|
|
201
|
+
const href = m["attrs"]["href"];
|
|
202
|
+
if (typeof href === "string" && href)
|
|
203
|
+
return href;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function cardUrl(attrs) {
|
|
209
|
+
if (!isRecord(attrs))
|
|
210
|
+
return undefined;
|
|
211
|
+
const url = attrs["url"];
|
|
212
|
+
if (typeof url === "string" && url)
|
|
213
|
+
return url;
|
|
214
|
+
const data = attrs["data"];
|
|
215
|
+
if (isRecord(data) && typeof data["url"] === "string" && data["url"]) {
|
|
216
|
+
return data["url"];
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
106
220
|
export function extractAcceptanceCriteria(fields, description) {
|
|
107
221
|
for (const [k, v] of Object.entries(fields)) {
|
|
108
|
-
if (!/acceptance/i.test(k))
|
|
222
|
+
if (!/acceptance|aceite|aceita[çc]/i.test(k))
|
|
109
223
|
continue;
|
|
110
224
|
const flat = flattenAdf(v);
|
|
111
225
|
const lines = toLines(flat);
|
|
112
226
|
if (lines.length)
|
|
113
227
|
return lines;
|
|
114
228
|
}
|
|
115
|
-
const m = description.match(/acceptance\s+criteria\s*:?\s*\n([\s\S]+?)(?:\n\s*\n|$)/i);
|
|
229
|
+
const m = description.match(/(?:acceptance\s+criteria|crit[ée]rios?\s+de\s+aceita[çc][ãa]o|crit[ée]rios?\s+de\s+aceite)\s*:?\s*\n([\s\S]+?)(?:\n\s*\n|$)/i);
|
|
116
230
|
if (m && m[1]) {
|
|
117
231
|
const lines = toLines(m[1]);
|
|
118
232
|
if (lines.length)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { mobileOpinionated } from "./mobile-opinionated.js";
|
|
2
|
-
export { getPreset, listPresets, knownPresetIds } from "./registry.js";
|
|
2
|
+
export { getPreset, resolvePresetId, listPresets, knownPresetIds, } from "./registry.js";
|
|
3
3
|
export { PRESET_SCHEMA_V1, PRESET_DIR_SUBDIR, PresetManifestError, parsePresetManifest, loadPresetFromDir, loadPresetsFromProject, materializePreset, } from "./loader.js";
|
|
4
4
|
export { stackPresets } from "./stacking.js";
|
|
5
5
|
export { renderPresetArticle, renderPresetArticles, renderPresetGatesForPlan, } from "./renderer.js";
|
|
@@ -2,8 +2,19 @@ import { mobileOpinionated } from "./mobile-opinionated.js";
|
|
|
2
2
|
const REGISTRY = {
|
|
3
3
|
[mobileOpinionated.id]: mobileOpinionated,
|
|
4
4
|
};
|
|
5
|
+
const ALIASES = {
|
|
6
|
+
mobile: mobileOpinionated.id,
|
|
7
|
+
};
|
|
8
|
+
export function resolvePresetId(id) {
|
|
9
|
+
if (!id)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (REGISTRY[id])
|
|
12
|
+
return id;
|
|
13
|
+
return ALIASES[id];
|
|
14
|
+
}
|
|
5
15
|
export function getPreset(id) {
|
|
6
|
-
|
|
16
|
+
const canonical = resolvePresetId(id);
|
|
17
|
+
return canonical ? REGISTRY[canonical] : undefined;
|
|
7
18
|
}
|
|
8
19
|
export function listPresets() {
|
|
9
20
|
return Object.values(REGISTRY);
|
package/dist/_core/package.json
CHANGED
|
@@ -88,18 +88,22 @@ and assets are incorporated, catalogued and present on disk — no loose ends.
|
|
|
88
88
|
{{/if}}
|
|
89
89
|
|
|
90
90
|
## Problem Statement
|
|
91
|
-
{{#if
|
|
92
|
-
{{
|
|
91
|
+
{{#if jiraTicketBody}}
|
|
92
|
+
{{jiraTicketBody}}
|
|
93
93
|
|
|
94
94
|
<!--
|
|
95
|
-
Imported VERBATIM from Jira {{jira}} —
|
|
96
|
-
|
|
95
|
+
Imported VERBATIM from Jira {{jira}} — the description PLUS every rich custom field
|
|
96
|
+
(scenarios, requirements, test caveats, documentation/design links) the card holds.
|
|
97
|
+
This is the authoritative source for this feature. The agent MUST redistribute ALL of
|
|
98
|
+
it into the canonical sections below, LOSING NOTHING (each `### <field>` block too):
|
|
97
99
|
- each user story / "História" → ## User Stories (as [US#] with priority, why, independent test)
|
|
98
100
|
- each functional requirement / "Requisito Funcional / RF" → ## Functional Requirements (as [FR-#])
|
|
99
101
|
- each non-functional requirement / "Requisito Não Funcional / RNF" → ## Non-Functional Requirements (as [NFR-#])
|
|
102
|
+
- each applicable scenario / "Cenário aplicável" → ## User Scenarios & Testing (and a covering [AC-#])
|
|
100
103
|
- each acceptance scenario / "Critério de Aceitação / Cenário" → ## Acceptance Criteria (as [AC-#] Given/When/Then, covering a [US#])
|
|
104
|
+
- test caveats / "Ressalvas de testes" → ## Edge Cases (or the relevant [NFR-#])
|
|
101
105
|
- the ticket's out-of-scope / "Fora do Escopo" list → ## Out of Scope
|
|
102
|
-
- design links (Figma etc.) → ## Dependencies & Assumptions
|
|
106
|
+
- design / documentation links (Figma etc.) → ## Dependencies & Assumptions
|
|
103
107
|
Then REDUCE this section to a crisp problem statement (who is affected today and how). Do not leave the raw dump here.
|
|
104
108
|
-->
|
|
105
109
|
{{else}}{{#if description}}
|
package/dist/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Flags } from "@oclif/core";
|
|
|
2
2
|
import { BaseCommand } from "../base-command.js";
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
|
-
import { appendAuditEvent, buildContextBlock, coreBlock, CORE_VERSION, createManifest, detectInstalledAgents, generateAgentsYaml, getAgentByKind, getPreset, hashString, knownPresetIds, materializeSlashCommands, renderBundled, renderPresetArticles, SELECTABLE_AGENT_KEYS, upsertContextFile, VERSION, } from "../_core/dist/index.js";
|
|
5
|
+
import { appendAuditEvent, buildContextBlock, coreBlock, CORE_VERSION, createManifest, detectInstalledAgents, generateAgentsYaml, getAgentByKind, getPreset, hashString, knownPresetIds, resolvePresetId, materializeSlashCommands, renderBundled, renderPresetArticles, SELECTABLE_AGENT_KEYS, upsertContextFile, VERSION, } from "../_core/dist/index.js";
|
|
6
6
|
import { currentActor } from "../shared/project.js";
|
|
7
7
|
import { installBundledExtensions, installShellScripts, installTemplates, } from "../shared/scaffold.js";
|
|
8
8
|
export default class Init extends BaseCommand {
|
|
@@ -85,12 +85,13 @@ export default class Init extends BaseCommand {
|
|
|
85
85
|
if (!preset) {
|
|
86
86
|
this.warn(`unknown preset "${flags.preset}" — known presets: ${knownPresetIds().join(", ")}. Constitution will have no preset articles.`);
|
|
87
87
|
}
|
|
88
|
+
const presetId = resolvePresetId(flags.preset) ?? flags.preset;
|
|
88
89
|
const presetArticles = preset ? renderPresetArticles(preset) : "";
|
|
89
90
|
const raptorYml = renderBundled("raptorYml", {
|
|
90
91
|
raptorVersion: VERSION,
|
|
91
92
|
projectName,
|
|
92
93
|
createdAt,
|
|
93
|
-
preset:
|
|
94
|
+
preset: presetId,
|
|
94
95
|
});
|
|
95
96
|
writeFileSync(join(raptorDir, "raptor.yml"), raptorYml);
|
|
96
97
|
const manifest = createManifest({
|
|
@@ -99,7 +100,7 @@ export default class Init extends BaseCommand {
|
|
|
99
100
|
integration: flags.ai,
|
|
100
101
|
script_type: flags.script,
|
|
101
102
|
branch_numbering: flags["branch-numbering"],
|
|
102
|
-
preset:
|
|
103
|
+
preset: presetId,
|
|
103
104
|
scripts_installed: [],
|
|
104
105
|
templates_installed: [],
|
|
105
106
|
extensions_registered: [],
|
|
@@ -109,14 +110,14 @@ export default class Init extends BaseCommand {
|
|
|
109
110
|
ai: flags.ai,
|
|
110
111
|
script_type: flags.script,
|
|
111
112
|
branch_numbering: flags["branch-numbering"],
|
|
112
|
-
preset:
|
|
113
|
+
preset: presetId,
|
|
113
114
|
no_git: flags["no-git"],
|
|
114
115
|
created_at: createdAt,
|
|
115
116
|
};
|
|
116
117
|
writeFileSync(join(raptorDir, "init-options.json"), JSON.stringify(initOptions, null, 2) + "\n");
|
|
117
118
|
const constitution = renderBundled("constitution", {
|
|
118
119
|
projectName,
|
|
119
|
-
preset:
|
|
120
|
+
preset: presetId,
|
|
120
121
|
coreBlock: coreBlock(),
|
|
121
122
|
presetArticles,
|
|
122
123
|
});
|
|
@@ -150,7 +151,7 @@ export default class Init extends BaseCommand {
|
|
|
150
151
|
writeFileSync(agentsYamlPath, agentsYamlContent);
|
|
151
152
|
const contextBlock = buildContextBlock({
|
|
152
153
|
projectName,
|
|
153
|
-
preset:
|
|
154
|
+
preset: presetId,
|
|
154
155
|
constitutionArticles: preset?.articles.map((a) => a.id),
|
|
155
156
|
});
|
|
156
157
|
for (const agent of targetAgents) {
|
|
@@ -169,7 +170,7 @@ export default class Init extends BaseCommand {
|
|
|
169
170
|
projectName,
|
|
170
171
|
adapter: agent,
|
|
171
172
|
constitutionArticles: preset?.articles.map((a) => a.id),
|
|
172
|
-
presetId:
|
|
173
|
+
presetId: presetId,
|
|
173
174
|
});
|
|
174
175
|
allMaterialized.push({
|
|
175
176
|
agentId: agent.id,
|
|
@@ -193,7 +194,7 @@ export default class Init extends BaseCommand {
|
|
|
193
194
|
],
|
|
194
195
|
meta: {
|
|
195
196
|
core_version: CORE_VERSION,
|
|
196
|
-
preset:
|
|
197
|
+
preset: presetId,
|
|
197
198
|
preset_version: preset?.version,
|
|
198
199
|
preset_articles: preset?.articles.map((a) => a.id),
|
|
199
200
|
project: projectName,
|
|
@@ -36,7 +36,7 @@ export default class JiraPull extends BaseCommand {
|
|
|
36
36
|
this.log(JSON.stringify(issue, null, 2));
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
const ctx = mapIssueToSpecContext(issue);
|
|
39
|
+
const ctx = mapIssueToSpecContext(issue, { fieldBuckets: conn.customFields });
|
|
40
40
|
this.log(`=== ${issue.key} — ${issue.summary} ===\n`);
|
|
41
41
|
if (issue.issueType)
|
|
42
42
|
this.log(` Type: ${issue.issueType}`);
|
|
@@ -56,5 +56,10 @@ export default class JiraPull extends BaseCommand {
|
|
|
56
56
|
for (const ac of ctx.jiraAcceptance)
|
|
57
57
|
this.log(` - ${ac}`);
|
|
58
58
|
}
|
|
59
|
+
for (const s of ctx.jiraCustomSections) {
|
|
60
|
+
this.log(`\n ${s.title} [${s.bucket}]:`);
|
|
61
|
+
for (const line of s.body.split("\n"))
|
|
62
|
+
this.log(` ${line}`);
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
}
|
package/dist/commands/new.js
CHANGED
|
@@ -138,7 +138,7 @@ export default class New extends BaseCommand {
|
|
|
138
138
|
? [
|
|
139
139
|
flags.desc,
|
|
140
140
|
jiraContext?.jiraSummary,
|
|
141
|
-
jiraContext?.
|
|
141
|
+
jiraContext?.jiraTicketBody,
|
|
142
142
|
]
|
|
143
143
|
.filter(Boolean)
|
|
144
144
|
.join("\n")
|
|
@@ -447,7 +447,12 @@ export default class New extends BaseCommand {
|
|
|
447
447
|
}
|
|
448
448
|
try {
|
|
449
449
|
const issue = await client.getJiraIssue(conn.cloudId, key);
|
|
450
|
-
return {
|
|
450
|
+
return {
|
|
451
|
+
ok: true,
|
|
452
|
+
context: mapIssueToSpecContext(issue, {
|
|
453
|
+
fieldBuckets: conn.customFields,
|
|
454
|
+
}),
|
|
455
|
+
};
|
|
451
456
|
}
|
|
452
457
|
catch (err) {
|
|
453
458
|
return {
|
package/dist/shared/jira.js
CHANGED
|
@@ -12,6 +12,7 @@ export function jiraConn(config) {
|
|
|
12
12
|
...(j.project_key ? { projectKey: j.project_key } : {}),
|
|
13
13
|
statusSync: j.status_sync !== false,
|
|
14
14
|
transitions: j.transitions ?? {},
|
|
15
|
+
customFields: j.custom_fields ?? {},
|
|
15
16
|
};
|
|
16
17
|
if (provider === "mcp") {
|
|
17
18
|
if (!j.mcp?.command)
|
|
@@ -112,6 +113,9 @@ function renderRefresh(issue) {
|
|
|
112
113
|
for (const ac of issue.acceptanceCriteria)
|
|
113
114
|
lines.push(`- ${ac}`);
|
|
114
115
|
}
|
|
116
|
+
for (const cf of issue.customFields ?? []) {
|
|
117
|
+
lines.push("", `## ${cf.name}`, "", cf.value);
|
|
118
|
+
}
|
|
115
119
|
if (issue.comments.length) {
|
|
116
120
|
lines.push("", "## Recent comments");
|
|
117
121
|
for (const c of issue.comments.slice(-5)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raptor-aios",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Raptor — Spec-Driven Development (SDD) CLI for modern mobile apps. Constitutional gates, audit trail, real verification (a11y/perf/stores/OS matrix), and AI-agent slash commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/scripts/prepare-npm.mjs
CHANGED