@webpieces/rules-config 0.3.291 → 0.3.292
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.292",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/review-json.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare class ReviewJson {
|
|
2
|
+
title: string;
|
|
2
3
|
riskScore: number;
|
|
3
4
|
riskLevel: string;
|
|
4
5
|
riskEmoji: string;
|
|
@@ -6,7 +7,7 @@ export declare class ReviewJson {
|
|
|
6
7
|
violations: string[];
|
|
7
8
|
risks: string[];
|
|
8
9
|
filesToReview: string[];
|
|
9
|
-
constructor(riskScore: number, riskLevel: string, riskEmoji: string, summary: string, violations: string[], risks: string[], filesToReview: string[]);
|
|
10
|
+
constructor(title: string, riskScore: number, riskLevel: string, riskEmoji: string, summary: string, violations: string[], risks: string[], filesToReview: string[]);
|
|
10
11
|
}
|
|
11
12
|
export declare function prDirFor(repoRoot: string, featureName: string): string;
|
|
12
13
|
export declare function reviewJsonPath(repoRoot: string, featureName: string): string;
|
package/src/review-json.js
CHANGED
|
@@ -16,6 +16,7 @@ const to_error_1 = require("./to-error");
|
|
|
16
16
|
// schema + instructions) and `wp-finish-upsert-pr` (which reads it to render the RISK section and
|
|
17
17
|
// post the PR). Data-only (per CLAUDE.md, classes for data).
|
|
18
18
|
class ReviewJson {
|
|
19
|
+
title; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)
|
|
19
20
|
riskScore; // 0–100, drives the risk bar
|
|
20
21
|
riskLevel; // 'green' | 'yellow' | 'red'
|
|
21
22
|
riskEmoji; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted
|
|
@@ -23,7 +24,8 @@ class ReviewJson {
|
|
|
23
24
|
violations; // pattern/architecture violations; length = the Pattern Violations count
|
|
24
25
|
risks;
|
|
25
26
|
filesToReview;
|
|
26
|
-
constructor(riskScore, riskLevel, riskEmoji, summary, violations, risks, filesToReview) {
|
|
27
|
+
constructor(title, riskScore, riskLevel, riskEmoji, summary, violations, risks, filesToReview) {
|
|
28
|
+
this.title = title;
|
|
27
29
|
this.riskScore = riskScore;
|
|
28
30
|
this.riskLevel = riskLevel;
|
|
29
31
|
this.riskEmoji = riskEmoji;
|
|
@@ -53,6 +55,7 @@ function reviewJsonSchemaHint(filePath) {
|
|
|
53
55
|
return (`Write your PR review to:\n ${filePath}\n\n` +
|
|
54
56
|
`with this exact JSON shape (riskEmoji optional — derived from riskLevel):\n\n` +
|
|
55
57
|
`{\n` +
|
|
58
|
+
` "title": "concise PR title describing the change (imperative, no branch names)",\n` +
|
|
56
59
|
` "riskScore": 0, // integer 0–100 (higher = riskier)\n` +
|
|
57
60
|
` "riskLevel": "green | yellow | red",\n` +
|
|
58
61
|
` "summary": "5–10 sentence review summary",\n` +
|
|
@@ -117,6 +120,9 @@ function loadReviewJson(filePath) {
|
|
|
117
120
|
? raw['riskEmoji']
|
|
118
121
|
: (EMOJI_FOR_LEVEL[level] ?? '🟡');
|
|
119
122
|
const summary = typeof raw['summary'] === 'string' ? raw['summary'] : '';
|
|
120
|
-
|
|
123
|
+
// Title is lenient: absent/blank → '' and the finish command falls back to the feature name, so an
|
|
124
|
+
// older review.json (pre-title field) never hard-fails; the schema hint still asks the AI for it.
|
|
125
|
+
const title = typeof raw['title'] === 'string' ? raw['title'].trim() : '';
|
|
126
|
+
return new ReviewJson(title, riskScore, level, emoji, summary, asStringArray(raw['violations']), asStringArray(raw['risks']), asStringArray(raw['filesToReview']));
|
|
121
127
|
}
|
|
122
128
|
//# sourceMappingURL=review-json.js.map
|
package/src/review-json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR, PR_INFO_DIR } from './constants';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// The AI-authored review for a PR. webpieces is AI-first, so unlike trytami (where a human command\n// calls Claude), the AI writes this file itself between `wp-start-upsert-pr` (which prints the\n// schema + instructions) and `wp-finish-upsert-pr` (which reads it to render the RISK section and\n// post the PR). Data-only (per CLAUDE.md, classes for data).\nexport class ReviewJson {\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n\n constructor(\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n ) {\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n }\n}\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// The per-feature PR working dir: `.webpieces/pr-info/<feature>`. Holds pr-body.md (rendered\n// dashboard) and review.json (AI-authored review). Nested under pr-info/ to keep `.webpieces/`\n// top level quiet. Shared so the start/finish commands and the AI agree on one location.\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, PR_INFO_DIR, featureName);\n}\n\n// Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name so the\n// AI and the finish command agree on the location without passing it around.\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(prDirFor(repoRoot, featureName), 'review.json');\n}\n\n// Copy-paste schema both commands print: wp-start-upsert-pr to instruct the AI to WRITE it,\n// wp-finish-upsert-pr to instruct the AI to FIX it when missing/invalid.\nexport function reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n}\n\n// webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\nfunction asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n}\n\n// Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError the AI can\n// act on (mirrors readRawConfig in config-file.ts — the established JSON-parse chokepoint).\n// webpieces-disable no-any-unknown -- returns the opaque parsed object; the caller narrows each field\nfunction parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n}\n\n/**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when the file\n * is missing, unparseable, or structurally wrong — the message is written straight back to the AI so\n * it can fix the file and re-run. Returns a fully-populated ReviewJson on success.\n */\nexport function loadReviewJson(filePath: string): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n\n return new ReviewJson(\n riskScore as number,\n level,\n emoji,\n summary,\n asStringArray(raw['violations']),\n asStringArray(raw['risks']),\n asStringArray(raw['filesToReview']),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"review-json.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/review-json.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { WEBPIECES_TMP_DIR, PR_INFO_DIR } from './constants';\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// The AI-authored review for a PR. webpieces is AI-first, so unlike trytami (where a human command\n// calls Claude), the AI writes this file itself between `wp-start-upsert-pr` (which prints the\n// schema + instructions) and `wp-finish-upsert-pr` (which reads it to render the RISK section and\n// post the PR). Data-only (per CLAUDE.md, classes for data).\nexport class ReviewJson {\n title: string; // human PR title describing the change; used as the `gh pr` title (empty → caller falls back)\n riskScore: number; // 0–100, drives the risk bar\n riskLevel: string; // 'green' | 'yellow' | 'red'\n riskEmoji: string; // '🟢' | '🟡' | '🔴' — derived from riskLevel when omitted\n summary: string; // rendered in the dashboard Summary section\n violations: string[]; // pattern/architecture violations; length = the Pattern Violations count\n risks: string[];\n filesToReview: string[];\n\n constructor(\n title: string,\n riskScore: number,\n riskLevel: string,\n riskEmoji: string,\n summary: string,\n violations: string[],\n risks: string[],\n filesToReview: string[],\n ) {\n this.title = title;\n this.riskScore = riskScore;\n this.riskLevel = riskLevel;\n this.riskEmoji = riskEmoji;\n this.summary = summary;\n this.violations = violations;\n this.risks = risks;\n this.filesToReview = filesToReview;\n }\n}\n\nconst RISK_LEVELS = ['green', 'yellow', 'red'] as const;\nconst EMOJI_FOR_LEVEL: Record<string, string> = { green: '🟢', yellow: '🟡', red: '🔴' };\n\n// The per-feature PR working dir: `.webpieces/pr-info/<feature>`. Holds pr-body.md (rendered\n// dashboard) and review.json (AI-authored review). Nested under pr-info/ to keep `.webpieces/`\n// top level quiet. Shared so the start/finish commands and the AI agree on one location.\nexport function prDirFor(repoRoot: string, featureName: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, PR_INFO_DIR, featureName);\n}\n\n// Absolute path of the review.json for a feature — beside pr-body.md, keyed by branch name so the\n// AI and the finish command agree on the location without passing it around.\nexport function reviewJsonPath(repoRoot: string, featureName: string): string {\n return path.join(prDirFor(repoRoot, featureName), 'review.json');\n}\n\n// Copy-paste schema both commands print: wp-start-upsert-pr to instruct the AI to WRITE it,\n// wp-finish-upsert-pr to instruct the AI to FIX it when missing/invalid.\nexport function reviewJsonSchemaHint(filePath: string): string {\n return (\n `Write your PR review to:\\n ${filePath}\\n\\n` +\n `with this exact JSON shape (riskEmoji optional — derived from riskLevel):\\n\\n` +\n `{\\n` +\n ` \"title\": \"concise PR title describing the change (imperative, no branch names)\",\\n` +\n ` \"riskScore\": 0, // integer 0–100 (higher = riskier)\\n` +\n ` \"riskLevel\": \"green | yellow | red\",\\n` +\n ` \"summary\": \"5–10 sentence review summary\",\\n` +\n ` \"violations\": [\"pattern/architecture violations you found (empty array if none)\"],\\n` +\n ` \"risks\": [\"notable risks (empty array if none)\"],\\n` +\n ` \"filesToReview\": [\"paths a human should look at (empty array if none)\"]\\n` +\n `}`\n );\n}\n\n// webpieces-disable no-any-unknown -- opaque parsed JSON value, narrowed to string[] here\nfunction asStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n // webpieces-disable no-any-unknown -- element of an opaque JSON array, narrowed by the type guard\n return value.filter((v: unknown): v is string => typeof v === 'string');\n}\n\n// Parse opaque AI-authored JSON, converting a SyntaxError into a readable InformAiError the AI can\n// act on (mirrors readRawConfig in config-file.ts — the established JSON-parse chokepoint).\n// webpieces-disable no-any-unknown -- returns the opaque parsed object; the caller narrows each field\nfunction parseReviewJson(raw: string, filePath: string): Record<string, unknown> {\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: convert JSON.parse SyntaxError to an InformAiError for the AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // webpieces-disable no-any-unknown -- parsed JSON is opaque until narrowed by the caller\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(\n `review.json is not valid JSON (${error.message}).\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n}\n\n/**\n * Load + validate the AI-authored review.json. Throws InformAiError (with the schema) when the file\n * is missing, unparseable, or structurally wrong — the message is written straight back to the AI so\n * it can fix the file and re-run. Returns a fully-populated ReviewJson on success.\n */\nexport function loadReviewJson(filePath: string): ReviewJson {\n if (!fs.existsSync(filePath)) {\n throw new InformAiError(\n `Required review.json not found.\\n\\n${reviewJsonSchemaHint(filePath)}\\n\\n` +\n `Then re-run: pnpm wp-finish-upsert-pr`,\n );\n }\n\n const raw = parseReviewJson(fs.readFileSync(filePath, 'utf8'), filePath);\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw new InformAiError(`review.json must be a JSON object.\\n\\n${reviewJsonSchemaHint(filePath)}`);\n }\n\n const errors: string[] = [];\n\n const riskScore = raw['riskScore'];\n if (typeof riskScore !== 'number' || !Number.isFinite(riskScore) || riskScore < 0 || riskScore > 100) {\n errors.push(`\"riskScore\" must be a number 0–100, got ${JSON.stringify(riskScore)}.`);\n }\n\n const riskLevel = raw['riskLevel'];\n if (typeof riskLevel !== 'string' || !RISK_LEVELS.includes(riskLevel as typeof RISK_LEVELS[number])) {\n errors.push(`\"riskLevel\" must be one of: ${RISK_LEVELS.join(', ')}.`);\n }\n\n if (errors.length > 0) {\n throw new InformAiError(\n `review.json has ${errors.length} error(s) — fix ALL, then re-run pnpm wp-finish-upsert-pr:\\n\\n` +\n errors.map((e: string): string => ` • ${e}`).join('\\n') +\n `\\n\\n${reviewJsonSchemaHint(filePath)}`,\n );\n }\n\n const level = riskLevel as string;\n const emoji = typeof raw['riskEmoji'] === 'string' && raw['riskEmoji'] !== ''\n ? (raw['riskEmoji'] as string)\n : (EMOJI_FOR_LEVEL[level] ?? '🟡');\n const summary = typeof raw['summary'] === 'string' ? (raw['summary'] as string) : '';\n // Title is lenient: absent/blank → '' and the finish command falls back to the feature name, so an\n // older review.json (pre-title field) never hard-fails; the schema hint still asks the AI for it.\n const title = typeof raw['title'] === 'string' ? (raw['title'] as string).trim() : '';\n\n return new ReviewJson(\n title,\n riskScore as number,\n level,\n emoji,\n summary,\n asStringArray(raw['violations']),\n asStringArray(raw['risks']),\n asStringArray(raw['filesToReview']),\n );\n}\n"]}
|
|
@@ -37,8 +37,16 @@ by the `redirect-how-to-merge-main` guard.
|
|
|
37
37
|
3. **`pnpm wp-start-upsert-pr`** — the PR flow: update from main (same 3-point engine), push, run an
|
|
38
38
|
advisory build gate, then tells you to write `review.json`. Never creates the PR itself. On a merge
|
|
39
39
|
conflict here, finish with `pnpm wp-finish-upsert-pr` (not `wp-update-end`).
|
|
40
|
-
4. **`pnpm wp-finish-upsert-pr`** — validates any in-progress merge, requires your `review.json
|
|
41
|
-
the **authoritative** build gate, pushes, and creates/updates the PR.
|
|
40
|
+
4. **`pnpm wp-finish-upsert-pr`** — validates any in-progress merge, requires your `review.json` (its
|
|
41
|
+
`title` becomes the PR title), runs the **authoritative** build gate, pushes, and creates/updates the PR.
|
|
42
|
+
|
|
43
|
+
## One branch name — local, remote, and PR are always identical
|
|
44
|
+
|
|
45
|
+
A sync does its work on a transient `<feature>Squash` branch and, when it finishes, force-pushes and
|
|
46
|
+
**renames back to `<feature>`** — so your local branch, `origin/<feature>`, and the PR head are always
|
|
47
|
+
the *same* name. There is no `wpN` generation suffix to reconcile. Each sync also leaves a numbered
|
|
48
|
+
pre-merge snapshot — `<feature>PreMerge`, `<feature>PreMerge2`, … — a local trail you can diff against
|
|
49
|
+
or delete (`git branch --list '<feature>PreMerge*'`); they are not auto-pruned.
|
|
42
50
|
|
|
43
51
|
## Two possible outcomes of a squash-update
|
|
44
52
|
|
|
@@ -51,11 +59,12 @@ decides who does the work.
|
|
|
51
59
|
```
|
|
52
60
|
you: pnpm wp-update-start
|
|
53
61
|
tool: assertCleanTree # you must have committed your own work first
|
|
54
|
-
tool:
|
|
62
|
+
tool: snapshot pre-merge state -> <feature>PreMerge[N] (numbered trail, never overwritten)
|
|
63
|
+
tool: checkout main; pull; create transient <feature>Squash off main
|
|
55
64
|
tool: git merge --squash <feature> # SUCCEEDS — stages the combined result
|
|
56
65
|
tool: git commit -m "Squash merge of <feature>" # <-- the TOOL commits (mechanical)
|
|
57
|
-
tool:
|
|
58
|
-
done —
|
|
66
|
+
tool: force-push to origin/<feature>; rename <feature>Squash back to <feature>
|
|
67
|
+
done — you are on <feature> again (== origin/<feature> == PR head; names always match).
|
|
59
68
|
```
|
|
60
69
|
|
|
61
70
|
There is **no `git add` here** — `git merge --squash` staged only the merge result, and the tool
|
|
@@ -65,17 +74,17 @@ commits exactly that. The commit is a pure function of your already-committed wo
|
|
|
65
74
|
|
|
66
75
|
```
|
|
67
76
|
you: pnpm wp-update-start
|
|
68
|
-
tool: assertCleanTree;
|
|
77
|
+
tool: assertCleanTree; snapshot -> <feature>PreMerge[N]; checkout main; pull; create <feature>Squash
|
|
69
78
|
tool: git merge --squash <feature> # CONFLICTS — conflicted files left with markers
|
|
70
79
|
tool: writes .webpieces/merge-info/<feature>/ (A=fork / B=feature / C=main context + diffs)
|
|
71
80
|
tool: writes .webpieces/instruct-ai/webpieces.mergeprocess.md, then exits (code 2)
|
|
72
|
-
---- hand-off to the AI ----
|
|
81
|
+
---- hand-off to the AI (you are on the transient <feature>Squash branch) ----
|
|
73
82
|
AI: read webpieces.mergeprocess.md
|
|
74
83
|
AI: per conflicted file -> resolve markers -> git add <file> -> write merge-explanation.md
|
|
75
84
|
AI: pnpm wp-update-end # standalone; in the wp-start-upsert-pr flow run wp-finish-upsert-pr instead
|
|
76
85
|
tool: validateResolution (no markers left, every file has an explanation)
|
|
77
86
|
tool: assertNoUntracked -> git add -u -> git commit "... (conflicts resolved)" # <-- TOOL commits
|
|
78
|
-
tool: build gate ->
|
|
87
|
+
tool: force-push origin/<feature>; rename <feature>Squash back to <feature>; build gate -> create/update PR
|
|
79
88
|
```
|
|
80
89
|
|
|
81
90
|
Who does what on the conflict path:
|