@plainconceptsplatform/agent-harness 3.8.0 → 3.8.1
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.
|
@@ -8,50 +8,102 @@ const LEGACY_MANIFEST = '.platform/manifest'
|
|
|
8
8
|
|
|
9
9
|
export class ContractError extends Error {}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
i += 1
|
|
25
|
-
continue
|
|
26
|
-
}
|
|
27
|
-
if (pattern[i + 2] === '/') {
|
|
28
|
-
source += '(?:.*/)?'
|
|
29
|
-
i += 2
|
|
30
|
-
continue
|
|
31
|
-
}
|
|
11
|
+
function bracket(segment, start) {
|
|
12
|
+
const end = segment.indexOf(']', start + 2)
|
|
13
|
+
if (end === -1) return null
|
|
14
|
+
let body = segment.slice(start + 1, end)
|
|
15
|
+
const negated = body[0] === '!' || body[0] === '^'
|
|
16
|
+
if (negated) body = body.slice(1)
|
|
17
|
+
const ranges = []
|
|
18
|
+
for (let i = 0; i < body.length; i++) {
|
|
19
|
+
const from = body[i] === '\\' && i + 1 < body.length ? body[++i] : body[i]
|
|
20
|
+
let to = from
|
|
21
|
+
if (body[i + 1] === '-' && i + 2 < body.length) {
|
|
22
|
+
i += 2
|
|
23
|
+
to = body[i] === '\\' && i + 1 < body.length ? body[++i] : body[i]
|
|
32
24
|
}
|
|
25
|
+
ranges.push([from, to])
|
|
26
|
+
}
|
|
27
|
+
return { token: { negated, ranges }, end }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const STAR = Symbol('*')
|
|
31
|
+
|
|
32
|
+
function segmentTokens(segment) {
|
|
33
|
+
const tokens = []
|
|
34
|
+
for (let i = 0; i < segment.length; i++) {
|
|
35
|
+
const c = segment[i]
|
|
36
|
+
const set = c === '[' ? bracket(segment, i) : null
|
|
33
37
|
if (c === '*') {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
if (tokens.at(-1) !== STAR) tokens.push(STAR)
|
|
39
|
+
} else if (c === '?') tokens.push({ negated: true, ranges: [] })
|
|
40
|
+
else if (set) {
|
|
41
|
+
tokens.push(set.token)
|
|
42
|
+
i = set.end
|
|
43
|
+
} else tokens.push(c === '\\' && i + 1 < segment.length ? segment[++i] : c)
|
|
44
|
+
}
|
|
45
|
+
return tokens
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tokenMatches = (token, c) => typeof token === 'string'
|
|
49
|
+
? token === c
|
|
50
|
+
: token.ranges.some(([from, to]) => from <= c && c <= to) !== token.negated
|
|
51
|
+
|
|
52
|
+
function matchSegment(tokens, text) {
|
|
53
|
+
let t = 0
|
|
54
|
+
let i = 0
|
|
55
|
+
let star = -1
|
|
56
|
+
let resume = 0
|
|
57
|
+
while (i < text.length) {
|
|
58
|
+
if (tokens[t] === STAR) {
|
|
59
|
+
star = t++
|
|
60
|
+
resume = i
|
|
61
|
+
} else if (t < tokens.length && tokenMatches(tokens[t], text[i])) {
|
|
62
|
+
t++
|
|
63
|
+
i++
|
|
64
|
+
} else if (star !== -1) {
|
|
65
|
+
t = star + 1
|
|
66
|
+
i = ++resume
|
|
67
|
+
} else return false
|
|
68
|
+
}
|
|
69
|
+
while (tokens[t] === STAR) t++
|
|
70
|
+
return t === tokens.length
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// gitwildmatch over `/` segments; a RegExp built from contract text is a blocking finding in the apps' Semgrep scan.
|
|
74
|
+
export function compileGlob(glob) {
|
|
75
|
+
const directory = glob.endsWith('/')
|
|
76
|
+
const body = directory ? glob.slice(0, -1) : glob
|
|
77
|
+
const segments = (body.startsWith('/') ? body.slice(1) : body).split('/')
|
|
78
|
+
return { anchored: body.includes('/'), directory, segments: segments.map(s => (s === '**' ? s : segmentTokens(s))) }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function matchFrom(segments, s, parts, p, directory) {
|
|
82
|
+
if (s === segments.length) return parts.length - p >= (directory ? 1 : 0)
|
|
83
|
+
if (segments[s] !== '**') return p < parts.length && matchSegment(segments[s], parts[p]) && matchFrom(segments, s + 1, parts, p + 1, directory)
|
|
84
|
+
for (let next = s + 1 === segments.length ? p + 1 : p; next <= parts.length; next++) {
|
|
85
|
+
if (matchFrom(segments, s + 1, parts, next, directory)) return true
|
|
86
|
+
}
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function matchGlob(compiled, file) {
|
|
91
|
+
const { anchored, directory, segments } = typeof compiled === 'string' ? compileGlob(compiled) : compiled
|
|
92
|
+
const parts = file.split('/')
|
|
93
|
+
for (let start = 0; start < (anchored ? 1 : parts.length); start++) {
|
|
94
|
+
if (matchFrom(segments, 0, parts, start, directory)) return true
|
|
43
95
|
}
|
|
44
|
-
return
|
|
96
|
+
return false
|
|
45
97
|
}
|
|
46
98
|
|
|
47
99
|
const toRules = globs => globs.map(glob => {
|
|
48
100
|
const negated = glob.startsWith('!')
|
|
49
|
-
return { negated,
|
|
101
|
+
return { negated, glob: compileGlob(negated ? glob.slice(1) : glob) }
|
|
50
102
|
})
|
|
51
103
|
|
|
52
104
|
function matchesRules(rules, file) {
|
|
53
105
|
let matched = false
|
|
54
|
-
for (const rule of rules) if (rule.
|
|
106
|
+
for (const rule of rules) if (matchGlob(rule.glob, file)) matched = !rule.negated
|
|
55
107
|
return matched
|
|
56
108
|
}
|
|
57
109
|
|
|
@@ -223,7 +275,7 @@ export function isOwned(contract, file, today = localDate()) {
|
|
|
223
275
|
const relative = file.replace(/\\/g, '/').replace(/^\.\//, '')
|
|
224
276
|
if (!matchesRules(contract.rules, relative)) return false
|
|
225
277
|
return !contract.exceptionList.some(e =>
|
|
226
|
-
isText(e.path) && /^\d{4}-\d{2}-\d{2}$/.test(String(e.expires ?? '')) && e.expires >= today &&
|
|
278
|
+
isText(e.path) && /^\d{4}-\d{2}-\d{2}$/.test(String(e.expires ?? '')) && e.expires >= today && matchGlob(e.path, relative))
|
|
227
279
|
}
|
|
228
280
|
|
|
229
281
|
export function describeOwner(contract) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/agent-harness",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.1",
|
|
4
4
|
"description": "Installs the Plain Concepts Platform Harness into any codebase, and keeps it up to date. Wires OpenCode, OpenSpec, codegraph, and agentmemory into a multi-agent workflow that runs on native parallel subagents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode",
|