@skitterbyte/skitterspec-linear 10.0.1 → 10.2.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/README.md +76 -15
- package/assets/core/SETUP.md +73 -13
- package/assets/core/linear.config.json.example +8 -1
- package/assets/core/linear.config.md +243 -8
- package/assets/rules/spec-planning.md +17 -11
- package/assets/skills/spec/SKILL.md +106 -66
- package/assets/skills/spec-bug/SKILL.md +99 -17
- package/assets/skills/spec-cancel/SKILL.md +34 -0
- package/assets/skills/spec-complete/SKILL.md +70 -15
- package/assets/skills/spec-go/SKILL.md +10 -3
- package/assets/skills/spec-hotfix/SKILL.md +157 -4
- package/assets/skills/spec-linear-setup/SKILL.md +172 -0
- package/assets/skills/spec-push/SKILL.md +142 -30
- package/assets/skills/spec-review/SKILL.md +34 -0
- package/assets/skills/spec-status/SKILL.md +9 -0
- package/bin/skitterspec-linear.js +19 -0
- package/package.json +1 -1
- package/src/cli.js +30 -19
- package/src/env/resolve.js +7 -2
- package/src/env/teardown.js +23 -9
- package/src/init.js +11 -1
- package/src/vendor/linear/api.js +246 -0
- package/src/vendor/linear/cli-sync.js +788 -3
- package/src/vendor/linear/config.js +127 -7
- package/src/vendor/sync-core/index.js +8 -1
- package/src/vendor/sync-core/src/normalize.js +291 -82
- package/src/vendor/sync-core/src/push.js +18 -1
- package/src/vendor/sync-core/src/tables.js +102 -0
- package/src/vendor/sync-core/src/task-block.js +18 -7
- package/src/vendor/sync-core/src/verify.js +83 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compare what Linear STORED against what we sent, and report lost text.
|
|
5
|
+
*
|
|
6
|
+
* Why this is not a pull. One-way sync's rule is about **authority**: Linear
|
|
7
|
+
* must never influence repo content. This reads a description back to *check*
|
|
8
|
+
* it — it merges nothing, writes nothing, and feeds nothing into the projection
|
|
9
|
+
* or the snapshot. The repo remains the only source of truth; the only output is
|
|
10
|
+
* a warning for a human. Without it, a parser that silently eats characters
|
|
11
|
+
* produces a mirror that looks pushed and is wrong, which is exactly how the
|
|
12
|
+
* nested-table corruption went unnoticed (see `tables.js`).
|
|
13
|
+
*
|
|
14
|
+
* The engine is offline, so the read itself belongs to the `/spec-push` skill —
|
|
15
|
+
* it fetches over MCP and hands the result here, the same split
|
|
16
|
+
* `--workspace-states` already uses.
|
|
17
|
+
*
|
|
18
|
+
* Pure: no I/O, no clock, no randomness.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Linear reserialises markdown on save. These transforms are all observed and
|
|
22
|
+
// all harmless, so they are normalised away BEFORE comparing — otherwise every
|
|
23
|
+
// push would report a false divergence.
|
|
24
|
+
function canonicalForCompare(text) {
|
|
25
|
+
return String(text == null ? '' : text)
|
|
26
|
+
.replace(/\r\n/g, '\n')
|
|
27
|
+
.split('\n')
|
|
28
|
+
.map((line) =>
|
|
29
|
+
line
|
|
30
|
+
// Ordered-list markers → a placeholder. Linear renumbers lists, and the
|
|
31
|
+
// digits it rewrites are alphanumeric, so a naive alphanumeric compare
|
|
32
|
+
// would flag its own benign reformat as data loss. Normalising the
|
|
33
|
+
// marker keeps digits significant EVERYWHERE ELSE — a port, a version, a
|
|
34
|
+
// key length still count.
|
|
35
|
+
.replace(/^(\s*)\d+\.(\s)/, '$1#.$2')
|
|
36
|
+
// Unordered markers unify (`-`/`+` come back as `*`).
|
|
37
|
+
.replace(/^(\s*)[*+-](\s)/, '$1-$2')
|
|
38
|
+
// Checkbox marks case-fold. Targeted rather than lowercasing the whole
|
|
39
|
+
// text, so a genuine case corruption in prose is still caught.
|
|
40
|
+
.replace(/^(\s*-\s*\[)[xX](\])/, '$1x$2')
|
|
41
|
+
// Table separator rows collapse (`|-------|` → `| -- |`).
|
|
42
|
+
.replace(/^\s*\|[\s:|-]+\|\s*$/, '|--|')
|
|
43
|
+
.replace(/[ \t]+$/, ''),
|
|
44
|
+
)
|
|
45
|
+
.join('\n')
|
|
46
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
47
|
+
.trim()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The word-character stream: everything that carries meaning, with every
|
|
51
|
+
// reformatting artefact (whitespace, bullets, asterisk boundaries, pipes,
|
|
52
|
+
// separators) removed. Comparing these catches dropped characters while
|
|
53
|
+
// ignoring every benign transform above.
|
|
54
|
+
function stream(text) {
|
|
55
|
+
return canonicalForCompare(text).replace(/[^\p{L}\p{N}]/gu, '')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} sent what we pushed
|
|
60
|
+
* @param {string} stored what the tracker returned
|
|
61
|
+
* @returns {{ok:boolean, at:number|null, lost:number, sentContext:string, storedContext:string}}
|
|
62
|
+
* `ok` false means word characters differ — content was lost or altered.
|
|
63
|
+
* `at` is the index in the reduced stream where they first diverge, with ~40
|
|
64
|
+
* characters of each side around it so the warning names the damage.
|
|
65
|
+
*/
|
|
66
|
+
function compareStored(sent, stored) {
|
|
67
|
+
const a = stream(sent)
|
|
68
|
+
const b = stream(stored)
|
|
69
|
+
if (a === b) return { ok: true, at: null, lost: 0, sentContext: '', storedContext: '' }
|
|
70
|
+
|
|
71
|
+
let at = 0
|
|
72
|
+
while (at < a.length && at < b.length && a[at] === b[at]) at++
|
|
73
|
+
const window = (s) => s.slice(Math.max(0, at - 10), at + 30)
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
at,
|
|
77
|
+
lost: a.length - b.length,
|
|
78
|
+
sentContext: window(a),
|
|
79
|
+
storedContext: window(b),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { compareStored, canonicalForCompare, stream }
|