@nitra/cursor 3.6.1 → 3.7.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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- flow: risk-aware глибина review — detectRisk у init + risk зі spec-frontmatter керують кількістю рецензентів (max за рівнем і ризиком) і безпековим фокусом промпта
|
|
8
|
+
|
|
3
9
|
## [3.6.1] - 2026-06-01
|
|
4
10
|
|
|
5
11
|
### Fixed
|
package/package.json
CHANGED
package/rules/flow/flow.mdc
CHANGED
|
@@ -21,9 +21,11 @@ Composer, Claude Code) працює **Пасивний Турнікет**: ти,
|
|
|
21
21
|
|
|
22
22
|
Створює ізольований worktree (`.worktrees/<branch>/`) і стан задачі. Якщо ти
|
|
23
23
|
вже в worktree — новий не вкладається. `init` визначає **рівень** задачі
|
|
24
|
-
(L0 тривіальне … L3 архітектурне)
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
(L0 тривіальне … L3 архітектурне) і **ризик** (low/med/high — за описом:
|
|
25
|
+
security/auth/secret → high) за описом: для L0 (fix/typo/bump) фази Spec/План
|
|
26
|
+
можна пропустити; для L≥1 вони рекомендовані. Рівень **і ризик** разом керують
|
|
27
|
+
глибиною та фокусом `review`. Ризик можна уточнити полем `risk:` у frontmatter
|
|
28
|
+
spec — `flow spec` його підхопить.
|
|
27
29
|
|
|
28
30
|
2. **Spec (дизайн)** — рекомендовано, не блокує. Brainstorm нашими термінами
|
|
29
31
|
(НЕ викликаючи superpowers):
|
|
@@ -72,9 +74,10 @@ Composer, Claude Code) працює **Пасивний Турнікет**: ти,
|
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
Незалежний субагент читає ЛИШЕ `git diff` від `base_commit` і шукає логічні
|
|
75
|
-
баги/ризики, яких не ловлять механічні гейти. Кількість рецензентів —
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
баги/ризики, яких не ловлять механічні гейти. Кількість рецензентів —
|
|
78
|
+
`max(рівень, ризик)` (L0→1 … L3→3; high-risk → 3, з безпековим фокусом
|
|
79
|
+
промпта). Findings пишуться у `.flow.json` (не блокує); high-severity варто
|
|
80
|
+
виправити перед фінішем.
|
|
78
81
|
|
|
79
82
|
7. **На провал** — виправ код за виводом і виклич `flow verify` знову. Максимум
|
|
80
83
|
**3 спроби**; якщо не вдається — зупинись і поклич людину.
|
|
@@ -12,7 +12,7 @@ import { cwd as processCwd } from 'node:process'
|
|
|
12
12
|
import { worktreePaths } from '../../lib/worktree.mjs'
|
|
13
13
|
import { worktreeFingerprint } from '../../utils/worktree-fingerprint.mjs'
|
|
14
14
|
import { flowEventsPath } from './events.mjs'
|
|
15
|
-
import { detectLevel } from './level.mjs'
|
|
15
|
+
import { detectLevel, detectRisk } from './level.mjs'
|
|
16
16
|
import { runReview } from './reviewer.mjs'
|
|
17
17
|
import { buildCompletionSnapshot, writeSummaryToTaskRecord } from './snapshot.mjs'
|
|
18
18
|
import { flowStatePath, readState, recordTransition, writeState } from './state-store.mjs'
|
|
@@ -103,15 +103,17 @@ export async function init(rest, deps = {}) {
|
|
|
103
103
|
const log = deps.log ?? console.error
|
|
104
104
|
const statePath = flowStatePath(ew.worktreeDir)
|
|
105
105
|
const level = detectLevel(ew.desc)
|
|
106
|
+
const risk = detectRisk(ew.desc)
|
|
106
107
|
writeState(statePath, {
|
|
107
108
|
branch: ew.branch,
|
|
108
109
|
status: 'in_progress',
|
|
109
110
|
started_at: new Date(now()).toISOString(),
|
|
110
111
|
metadata: { base_commit: ew.baseCommit },
|
|
111
112
|
level,
|
|
113
|
+
risk,
|
|
112
114
|
plan: []
|
|
113
115
|
})
|
|
114
|
-
log(`init: ${ew.branch} (level ${level}) → ${statePath}`)
|
|
116
|
+
log(`init: ${ew.branch} (level ${level}, risk ${risk}) → ${statePath}`)
|
|
115
117
|
return 0
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scale-adaptive рівень задачі (ідея з BMAD project-levels,
|
|
3
|
-
* `init` визначає рівень за описом;
|
|
4
|
-
*
|
|
2
|
+
* Scale-adaptive рівень + ризик задачі (ідея з BMAD project-levels/risk-profile,
|
|
3
|
+
* у наших термінах). `init` визначає рівень і ризик за описом; разом вони
|
|
4
|
+
* right-size'ять, скільки adversarial-рецензентів спавнить `flow review` (і його
|
|
5
|
+
* фокус), і які фази рекомендовані (контракт).
|
|
5
6
|
*
|
|
6
7
|
* Детекція — підрядками (case-insensitive), без regex (уникаємо slow-regex і
|
|
7
8
|
* проблем зі словомежами для кирилиці).
|
|
@@ -30,7 +31,7 @@ export function detectLevel(desc) {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
* Скільки adversarial-рецензентів спавнити для рівня (глибина review за
|
|
34
|
+
* Скільки adversarial-рецензентів спавнити для рівня (глибина review за розміром).
|
|
34
35
|
* @param {number} level рівень 0..3
|
|
35
36
|
* @returns {number} кількість рецензентів (1..3)
|
|
36
37
|
*/
|
|
@@ -39,3 +40,42 @@ export function reviewersForLevel(level) {
|
|
|
39
40
|
if (level === 2) return 2
|
|
40
41
|
return 1
|
|
41
42
|
}
|
|
43
|
+
|
|
44
|
+
/** Ключові слова високого ризику (безпека/гроші/доступи). */
|
|
45
|
+
const HIGH_RISK_KEYS = ['security', 'auth', 'crypto', 'payment', 'secret', 'token', 'permission', 'password', 'безпек']
|
|
46
|
+
/** Ключові слова середнього ризику (дані/незворотність). */
|
|
47
|
+
const MED_RISK_KEYS = ['data', ' db', 'database', 'migration', 'delete', 'gateway', 'міграц', 'видален']
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Рівень ризику задачі за описом: low | med | high.
|
|
51
|
+
* @param {string} desc опис задачі
|
|
52
|
+
* @returns {'low' | 'med' | 'high'} ризик
|
|
53
|
+
*/
|
|
54
|
+
export function detectRisk(desc) {
|
|
55
|
+
const d = String(desc ?? '').toLowerCase()
|
|
56
|
+
const has = keys => keys.some(k => d.includes(k))
|
|
57
|
+
if (has(HIGH_RISK_KEYS)) return 'high'
|
|
58
|
+
if (has(MED_RISK_KEYS)) return 'med'
|
|
59
|
+
return 'low'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Скільки рецензентів диктує сам ризик.
|
|
64
|
+
* @param {string} risk low|med|high
|
|
65
|
+
* @returns {number} 1..3
|
|
66
|
+
*/
|
|
67
|
+
export function reviewersForRisk(risk) {
|
|
68
|
+
if (risk === 'high') return 3
|
|
69
|
+
if (risk === 'med') return 2
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Підсумкова глибина review: максимум вимог за рівнем і за ризиком (кап 3).
|
|
75
|
+
* @param {number} level рівень 0..3
|
|
76
|
+
* @param {string} [risk] low|med|high
|
|
77
|
+
* @returns {number} кількість рецензентів (1..3)
|
|
78
|
+
*/
|
|
79
|
+
export function reviewersFor(level, risk) {
|
|
80
|
+
return Math.min(3, Math.max(reviewersForLevel(level), reviewersForRisk(risk)))
|
|
81
|
+
}
|
|
@@ -11,7 +11,7 @@ import { cwd as processCwd } from 'node:process'
|
|
|
11
11
|
|
|
12
12
|
import { realRun } from './commands.mjs'
|
|
13
13
|
import { flowEventsPath } from './events.mjs'
|
|
14
|
-
import {
|
|
14
|
+
import { reviewersFor } from './level.mjs'
|
|
15
15
|
import { flowStatePath, readState, recordTransition } from './state-store.mjs'
|
|
16
16
|
import { createRunner } from './subagent-runner.mjs'
|
|
17
17
|
|
|
@@ -32,18 +32,27 @@ export function diffFromBase(base, run, cwd) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Промпт adversarial-рецензента (читає ЛИШЕ diff).
|
|
35
|
+
* Промпт adversarial-рецензента (читає ЛИШЕ diff). Для high-risk додає
|
|
36
|
+
* безпекову лінзу.
|
|
36
37
|
* @param {string} diff текст diff
|
|
38
|
+
* @param {string} [risk] low|med|high — фокус перевірки
|
|
37
39
|
* @returns {string} промпт
|
|
38
40
|
*/
|
|
39
|
-
export function reviewerPrompt(diff) {
|
|
41
|
+
export function reviewerPrompt(diff, risk) {
|
|
42
|
+
const lens =
|
|
43
|
+
risk === 'high'
|
|
44
|
+
? 'ОСОБЛИВА УВАГА БЕЗПЕЦІ: auth/доступи, секрети/токени, ін\'єкції, валідація входу, незворотні операції.'
|
|
45
|
+
: ''
|
|
40
46
|
return [
|
|
41
47
|
'Ти — прискіпливий adversarial-рецензент. Знайди баги, ризики й smells ЛИШЕ в цьому diff.',
|
|
48
|
+
lens,
|
|
42
49
|
'Поверни ЛИШЕ JSON-масив: [{ "severity": "high|med|low", "file": "...", "issue": "...", "suggestion": "..." }].',
|
|
43
50
|
'Якщо проблем нема — поверни [].',
|
|
44
51
|
'',
|
|
45
52
|
diff.slice(0, DIFF_LIMIT)
|
|
46
|
-
]
|
|
53
|
+
]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join('\n')
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
/**
|
|
@@ -128,8 +137,8 @@ export async function review(_rest, deps = {}) {
|
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
const reviewers =
|
|
132
|
-
const prompt = reviewerPrompt(diff)
|
|
140
|
+
const reviewers = reviewersFor(state.level ?? 1, state.risk)
|
|
141
|
+
const prompt = reviewerPrompt(diff, state.risk)
|
|
133
142
|
const results = await Promise.all(Array.from({ length: reviewers }, () => runner.runStep(prompt, { cwd })))
|
|
134
143
|
const findings = dedupeFindings(results.flatMap(r => (r.ok ? parseFindings(r.output) : [])))
|
|
135
144
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Brainstorm: human↔agent — у діалозі IDE-агента (контракт); agent↔agent —
|
|
8
8
|
* `--panel` (панель персон → суддя, синтез презентується людині).
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync } from 'node:fs'
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
11
11
|
import { cwd as processCwd } from 'node:process'
|
|
12
12
|
|
|
13
13
|
import { resolveArtifact, verifyTrace } from './artifact.mjs'
|
|
@@ -15,6 +15,26 @@ import { flowEventsPath } from './events.mjs'
|
|
|
15
15
|
import { runPanel } from './plan-panel.mjs'
|
|
16
16
|
import { createRunner } from './subagent-runner.mjs'
|
|
17
17
|
import { flowStatePath, readState, recordTransition } from './state-store.mjs'
|
|
18
|
+
import { parseFrontMatter } from '../trace.mjs'
|
|
19
|
+
|
|
20
|
+
/** Допустимі значення ризику у spec-frontmatter. */
|
|
21
|
+
const RISKS = new Set(['low', 'med', 'high'])
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Зчитує `risk` зі spec-frontmatter, якщо валідний (інакше — поточний у стані).
|
|
25
|
+
* Так risk-нотатка у spec керує глибиною подальшого `flow review`.
|
|
26
|
+
* @param {string} doc шлях spec-doc
|
|
27
|
+
* @param {string | undefined} current поточний risk у стані
|
|
28
|
+
* @returns {string | undefined} ризик
|
|
29
|
+
*/
|
|
30
|
+
function riskFromSpec(doc, current) {
|
|
31
|
+
try {
|
|
32
|
+
const fm = parseFrontMatter(readFileSync(doc, 'utf8'))
|
|
33
|
+
return fm && RISKS.has(fm.risk) ? fm.risk : current
|
|
34
|
+
} catch {
|
|
35
|
+
return current
|
|
36
|
+
}
|
|
37
|
+
}
|
|
18
38
|
|
|
19
39
|
/**
|
|
20
40
|
* @param {string[]} rest аргументи (`--panel`, опц. `<spec.md>`)
|
|
@@ -57,12 +77,13 @@ export async function spec(rest, deps = {}) {
|
|
|
57
77
|
log('⚠️ spec: trace виявив розрив ланцюга — перевір лінки front-matter (adr/spec/plan)')
|
|
58
78
|
}
|
|
59
79
|
|
|
80
|
+
const risk = riskFromSpec(doc, state.risk)
|
|
60
81
|
recordTransition(
|
|
61
82
|
{ statePath, eventsPath: flowEventsPath(cwd) },
|
|
62
83
|
{ type: 'spec' },
|
|
63
|
-
s => ({ ...s, spec_doc: doc, status: 'spec' }),
|
|
84
|
+
s => ({ ...s, spec_doc: doc, risk, status: 'spec' }),
|
|
64
85
|
deps.now ?? Date.now
|
|
65
86
|
)
|
|
66
|
-
log(`spec: зафіксовано ${doc} → status: spec`)
|
|
87
|
+
log(`spec: зафіксовано ${doc} → status: spec (risk ${risk ?? '—'})`)
|
|
67
88
|
return 0
|
|
68
89
|
}
|