@mjasnikovs/pi-task 0.42.7 → 0.42.8

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.
@@ -21,22 +21,37 @@
21
21
  const TEST_NOUN = /\b(?:tests?|suites?|assertions?)\b/i;
22
22
  const BUILD_NOUN = /\b(?:lint|linter|typecheck|build|ci)\b/i;
23
23
  const FAILURE = /\b(?:fail\w*|red|broken|breaks?|breakage|errors?)\b/i;
24
- /** Before a phrase in its clause: the phrase is rejected. */
25
- const NOT_A_DECISION = /\b(?:not|never|no|don't|do not|doesn't|does not|rather than|instead of|isn't|is not|without|avoid|avoiding)\b/i;
24
+ /** Before a phrase in its clause: the phrase is rejected. Models emit U+2019 as
25
+ * readily as ASCII, so a contraction must not turn the verdict on the character. */
26
+ const NOT_A_DECISION = /\b(?:not|never|no|do(?:es)?n['’]t|do(?:es)? not|won['’]t|will not|wouldn['’]t|would not|rather than|instead of|isn['’]t|is not|without|avoid|avoiding)\b/i;
26
27
  /**
27
- * A modal cancels a phrase only where the sentence poses an option for it to
28
- * weigh: "IF NOT EXISTS would still leave the test failing" describes what a
29
- * rejected option does. A bare hedge does not — "I would flag it as a known
30
- * issue" is the decision, and treating every modal as hypothetical let the guard
31
- * be rephrased away.
28
+ * A modal cancels a phrase only where the sentence is weighing something: "IF NOT
29
+ * EXISTS would still leave the test failing" describes what a rejected option
30
+ * does. A bare hedge does not — "I would flag it as a known issue" is the
31
+ * decision, and treating every modal as hypothetical let the guard be rephrased
32
+ * away.
32
33
  */
33
34
  const MODAL = /\b(?:would|could|might)\b/i;
34
- /** A condition governs its own half of a semicolon and reaches no further. */
35
+ /** A condition governs the clauses it opens and stops at the conclusion drawn. */
35
36
  const CONDITION = /\b(?:if|unless)\b/i;
36
- /** Options are posed once and weighed in any later clause of the sentence. */
37
- const OPTION = /\b(?:either|whether|option|alternative|otherwise)\b/i;
38
- /** Whatever the sentence weighs, "I would …" is the answer's own decision. */
37
+ /** Options are posed once and weighed anywhere in the sentence. */
38
+ const OPTION = /\b(?:either|whether|options?|alternatives?|otherwise)\b/i;
39
+ /** Under a condition the subject says whose modal it is: "the latter would leave
40
+ * it red" is the option behaving, "I would leave it red" is the answer deciding. */
39
41
  const FIRST_PERSON = /\b(?:i|we)\s+(?:would|could|might)\b/i;
42
+ /**
43
+ * The repair the sentence goes on to choose. An option's consequence is a
44
+ * hypothesis only where the sentence chooses against it; a deferral that nothing
45
+ * overrules is the decision, however the sentence hedges it. Checking for the
46
+ * choice rather than for a pronoun is what stops "it could be left failing for
47
+ * whoever owns it" from riding out on one option word.
48
+ */
49
+ const CHOOSES_REPAIR = /\b(?:i|we)(?:\s+will|\s+shall|['’]ll)\s+(?:update|fix|change|add|adjust|amend|correct)\b|\bso\s+(?:update|fix|change|adjust|amend|correct)\b/i;
50
+ /** Where a hypothesis's reach ends: a semicolon starts an independent clause, and
51
+ * "so" introduces the conclusion drawn, not another branch of the hypothesis.
52
+ * Neither is a comma, which is why scoping a condition by punctuation alone let
53
+ * "fails only if X, so it could be flagged as known" through. */
54
+ const HYPOTHESIS_END = /;|\bso\b|\btherefore\b|\bhence\b|\bthus\b/i;
40
55
  /** Where one clause ends and the next begins. A semicolon joins clauses of ONE
41
56
  * thought, so the breakage a clause defers may sit in the other half. */
42
57
  const CLAUSE_BOUNDARY = /[,:;()]|\s[—–-]\s|\b(?:and|but|so|then|while|whereas|although|though|because|since|however)\b/gi;
@@ -59,11 +74,15 @@ const PHRASES = [
59
74
  // Handing the work to an unnamed someone is the deferral itself, whatever the
60
75
  // clause is about; bare `whoever` below still needs a check to be one.
61
76
  { re: /\bwhoever\s+(?:owns|revisits|maintains|touches)\b/i, needs: 'alone' },
62
- // Ownership handed to a ROLE is handed to nobody: no release manager exists in
63
- // a /task-auto run. A named team is someone, so the lookahead hands those to
64
- // the `check` rule below rather than listing every job title here.
77
+ // Ownership handed to a ROLE is handed to nobody: no release manager, QA or
78
+ // on-call engineer sits in a /task-auto run. Anyone else the answer names a
79
+ // person, a team, the runner, this task is ownership TAKEN, so the phrase
80
+ // alone is not the deferral. The list names the nobodies rather than exempting
81
+ // the somebodies, because the two misses cost differently: a role it does not
82
+ // know still reaches the `check` rule below, while a somebody it mistakes for
83
+ // nobody destroys the answer this guard exists to protect.
65
84
  {
66
- re: /\bownership\s+(?:belongs|lies|rests)\s+(?:to|with)\b(?!.*\b(?:teams?|groups?|squads?|guilds?|crews?)\b)/i,
85
+ re: /\bownership\s+(?:belongs|lies|rests)\s+(?:to|with)\s+(?:whoever|someone|somebody|anyone|another\b|a\s+(?:later|future|separate)\b|(?:the\s+)?(?:release\s+manager|on[- ]call|qa|sre)\b|the\s+(?:\w+\s+)?(?:owners?|maintainers?)\b)/i,
67
86
  needs: 'alone'
68
87
  },
69
88
  { re: /\bownership\s+(?:belongs|lies|rests)\s+(?:to|with)\b/i, needs: 'check' },
@@ -89,10 +108,12 @@ function aboutACheck(text) {
89
108
  /**
90
109
  * Parenthetical asides go, and a code span keeps its words but loses the
91
110
  * punctuation that would split a clause in two: `toEqual([{filename: X}])` is
92
- * one token of the sentence around it, not three clauses.
111
+ * one token of the sentence around it, not three clauses. Brackets close up
112
+ * instead of spacing, because a bracket binds to what it encloses — spacing
113
+ * `arr[i]` leaves a bare `i` for the first-person rule to read as a pronoun.
93
114
  */
94
115
  function prose(answer) {
95
- let text = answer.replace(/`([^`]*)`/g, (_m, code) => code.replace(/[,;:()[\]{}]/g, ' '));
116
+ let text = answer.replace(/`([^`]*)`/g, (_m, code) => code.replace(/[[\]{}]/g, '').replace(/[,;:()]/g, ' '));
96
117
  let before;
97
118
  do {
98
119
  before = text;
@@ -106,13 +127,16 @@ function clauses(sentence) {
106
127
  export function defersBreakage(answer) {
107
128
  for (const sentence of prose(answer).split(SENTENCE_BOUNDARY)) {
108
129
  const sentenceBreaks = aboutACheck(sentence) && FAILURE.test(sentence);
109
- const posesOptions = OPTION.test(sentence);
110
- // A semicolon joins two independent clauses, so "the test fails only if X;
111
- // it could be flagged as known" states a condition and then decides. The
112
- // options the sentence posed reach across it; the condition does not.
113
- for (const half of sentence.split(';')) {
114
- const weighsOptions = posesOptions || CONDITION.test(half);
115
- for (const clause of clauses(half)) {
130
+ const weighsRejectedOption = OPTION.test(sentence) && CHOOSES_REPAIR.test(sentence);
131
+ const cancelsModal = (before, underCondition) => (underCondition && !FIRST_PERSON.test(before)) || weighsRejectedOption;
132
+ // A condition governs the clauses it opens and stops at the conclusion the
133
+ // sentence draws, so "fails only if the fixture is stale, so it could be
134
+ // flagged as known" states a condition and then decides. An option is posed
135
+ // once and weighed anywhere, but only where the sentence goes on to pick a
136
+ // repair is the modal describing the branch it rejected.
137
+ for (const segment of sentence.split(HYPOTHESIS_END)) {
138
+ const underCondition = CONDITION.test(segment);
139
+ for (const clause of clauses(segment)) {
116
140
  for (const { re, needs } of PHRASES) {
117
141
  const hit = re.exec(clause);
118
142
  if (!hit)
@@ -120,7 +144,7 @@ export function defersBreakage(answer) {
120
144
  const before = clause.slice(0, hit.index);
121
145
  if (NOT_A_DECISION.test(before))
122
146
  continue;
123
- if (MODAL.test(before) && weighsOptions && !FIRST_PERSON.test(before))
147
+ if (MODAL.test(before) && cancelsModal(before, underCondition))
124
148
  continue;
125
149
  if (needs === 'check' && !aboutACheck(clause))
126
150
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.42.7",
3
+ "version": "0.42.8",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",