mandrel 1.81.0 → 1.83.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.
Files changed (121) hide show
  1. package/.agents/README.md +46 -5
  2. package/.agents/docs/SDLC.md +97 -82
  3. package/.agents/docs/agentrc-reference.json +10 -2
  4. package/.agents/docs/configuration.md +4 -1
  5. package/.agents/docs/execution-reference.md +52 -0
  6. package/.agents/docs/workflows.md +1 -1
  7. package/.agents/instructions.md +85 -45
  8. package/.agents/personas/architect.md +8 -5
  9. package/.agents/personas/engineer-mobile.md +3 -2
  10. package/.agents/personas/engineer-web.md +3 -2
  11. package/.agents/personas/engineer.md +6 -5
  12. package/.agents/personas/product.md +19 -13
  13. package/.agents/personas/project-manager.md +9 -8
  14. package/.agents/personas/qa-engineer.md +10 -6
  15. package/.agents/personas/refactorer.md +3 -2
  16. package/.agents/personas/technical-writer.md +2 -1
  17. package/.agents/personas/ux-designer.md +2 -2
  18. package/.agents/schemas/agentrc.schema.json +41 -3
  19. package/.agents/schemas/qa-ledger.schema.json +2 -2
  20. package/.agents/scripts/acceptance-spec-reconciler.js +143 -59
  21. package/.agents/scripts/epic-deliver-prepare.js +40 -31
  22. package/.agents/scripts/epic-plan-decompose.js +2 -5
  23. package/.agents/scripts/epic-plan-spec.js +16 -19
  24. package/.agents/scripts/hierarchy-gate.js +11 -11
  25. package/.agents/scripts/lib/ITicketingProvider.js +4 -3
  26. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  27. package/.agents/scripts/lib/bdd-scenario-scanner.js +1 -1
  28. package/.agents/scripts/lib/cli-args.js +1 -5
  29. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  30. package/.agents/scripts/lib/config/explain.js +4 -1
  31. package/.agents/scripts/lib/config/temp-paths.js +1 -4
  32. package/.agents/scripts/lib/config-settings-schema.js +30 -1
  33. package/.agents/scripts/lib/epic-body-sections.js +310 -0
  34. package/.agents/scripts/lib/epic-plan-clarity.js +38 -1
  35. package/.agents/scripts/lib/epic-plan-ideation.js +15 -3
  36. package/.agents/scripts/lib/findings/promote-finding.js +3 -3
  37. package/.agents/scripts/lib/findings/severity.js +5 -6
  38. package/.agents/scripts/lib/label-constants.js +7 -17
  39. package/.agents/scripts/lib/label-taxonomy.js +4 -21
  40. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +65 -2
  41. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +105 -9
  42. package/.agents/scripts/lib/orchestration/doc-reader.js +29 -0
  43. package/.agents/scripts/lib/orchestration/docs-digest.js +134 -0
  44. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +23 -22
  45. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +7 -10
  46. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +4 -38
  47. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +8 -9
  48. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/authoring-context.js +11 -5
  49. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/cli-args.js +26 -5
  50. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +102 -304
  51. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +32 -29
  52. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +19 -20
  53. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-authoring-grounding.js +1 -1
  54. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +6 -9
  55. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +3 -4
  56. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +1 -1
  57. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +20 -27
  58. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +11 -5
  59. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +22 -59
  60. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  61. package/.agents/scripts/lib/orchestration/planning-context-budget.js +1 -1
  62. package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
  63. package/.agents/scripts/lib/orchestration/spec-freshness.js +3 -3
  64. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  65. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +15 -1
  66. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +122 -1
  67. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -8
  68. package/.agents/scripts/lib/orchestration/ticketing/reads.js +2 -2
  69. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -2
  70. package/.agents/scripts/lib/qa/console-allowlist.js +5 -4
  71. package/.agents/scripts/lib/qa/qa-context-hydrator.js +6 -85
  72. package/.agents/scripts/lib/qa/resolve-qa-contract.js +144 -8
  73. package/.agents/scripts/lib/templates/decomposer-prompts.js +14 -8
  74. package/.agents/scripts/lifecycle-emit.js +1 -1
  75. package/.agents/scripts/lint-label-vocabulary.js +2 -3
  76. package/.agents/scripts/providers/github/mappers.js +0 -3
  77. package/.agents/scripts/providers/github/tickets.js +7 -18
  78. package/.agents/scripts/single-story-init.js +0 -1
  79. package/.agents/scripts/story-init.js +1 -29
  80. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +43 -22
  81. package/.agents/skills/core/epic-plan-consolidate/examples.md +51 -0
  82. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +27 -40
  83. package/.agents/skills/core/epic-plan-decompose-author/examples.md +47 -0
  84. package/.agents/skills/core/epic-plan-premortem/SKILL.md +15 -13
  85. package/.agents/skills/core/epic-plan-premortem/examples.md +53 -0
  86. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +143 -151
  87. package/.agents/skills/core/epic-plan-spec-author/examples.md +91 -0
  88. package/.agents/skills/core/hydrate-context/SKILL.md +10 -5
  89. package/.agents/skills/core/knowledge-transfer/SKILL.md +3 -2
  90. package/.agents/skills/core/scope-triage/SKILL.md +2 -1
  91. package/.agents/skills/skills.index.json +8 -8
  92. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +52 -38
  93. package/.agents/templates/epic-from-idea.md +4 -0
  94. package/.agents/workflows/audit-to-stories.md +2 -2
  95. package/.agents/workflows/helpers/code-review.md +11 -9
  96. package/.agents/workflows/helpers/deliver-epic-reference.md +514 -0
  97. package/.agents/workflows/helpers/deliver-epic.md +173 -490
  98. package/.agents/workflows/helpers/epic-audit.md +11 -8
  99. package/.agents/workflows/helpers/epic-deliver-story.md +45 -27
  100. package/.agents/workflows/helpers/epic-plan-decompose.md +17 -12
  101. package/.agents/workflows/helpers/epic-plan-spec.md +68 -68
  102. package/.agents/workflows/helpers/parallel-tooling.md +2 -1
  103. package/.agents/workflows/helpers/plan-epic-reference.md +136 -0
  104. package/.agents/workflows/helpers/plan-epic.md +141 -256
  105. package/.agents/workflows/helpers/plan-story.md +31 -61
  106. package/.agents/workflows/helpers/qa-run-scenario.md +194 -0
  107. package/.agents/workflows/helpers/scope-triage-gate.md +97 -0
  108. package/.agents/workflows/helpers/single-story-deliver-reference.md +423 -0
  109. package/.agents/workflows/helpers/single-story-deliver.md +129 -393
  110. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -1
  111. package/.agents/workflows/plan.md +8 -8
  112. package/.agents/workflows/qa-assist.md +2 -1
  113. package/.agents/workflows/qa-explore.md +63 -32
  114. package/.agents/workflows/qa-run.md +293 -130
  115. package/docs/CHANGELOG.md +35 -0
  116. package/package.json +1 -1
  117. package/.agents/schemas/qa-finding.schema.json +0 -133
  118. package/.agents/scripts/lib/issue-link-parser.js +0 -74
  119. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +0 -116
  120. package/.agents/scripts/lib/orchestration/planning-state-manager.js +0 -318
  121. package/.agents/scripts/lib/story-init/hierarchy-tracer.js +0 -57
@@ -5,7 +5,7 @@
5
5
  * Exports:
6
6
  * - `assertDecomposeInputs(epic, epicId, tickets)` — entry guards.
7
7
  * - `buildEpicSpecInput(epic, epicId)` — projection used by
8
- * `renderSpec`; runs `ensurePlanningArtifacts` defensively so the
8
+ * `renderSpec`; the Epic body already carries the folded planning
9
9
  * spec body carries the `## Planning Artifacts` section the
10
10
  * cascade-close path depends on.
11
11
  * - `validateTickets(tickets, config)` — runs the cross-link / freshness
@@ -19,6 +19,7 @@
19
19
  * @module lib/orchestration/epic-plan-decompose/phases/persist-helpers
20
20
  */
21
21
 
22
+ import { hasTechSpecContent } from '../../../epic-body-sections.js';
22
23
  import { gitSpawn } from '../../../git-utils.js';
23
24
  import { Logger } from '../../../Logger.js';
24
25
  import { TYPE_LABELS } from '../../../label-constants.js';
@@ -29,10 +30,7 @@ import {
29
30
  } from '../../epic-plan-state-store.js';
30
31
  import { validateTaskBodies } from '../../task-body-validator.js';
31
32
  import { validateAndNormalizeTickets } from '../../ticket-validator.js';
32
- import {
33
- ensurePlanningArtifacts,
34
- resolveConflictPolicy,
35
- } from './planning-artifacts.js';
33
+ import { resolveConflictPolicy } from './planning-artifacts.js';
36
34
 
37
35
  export function assertDecomposeInputs(epic, epicId, tickets) {
38
36
  if (!epic) {
@@ -43,9 +41,9 @@ export function assertDecomposeInputs(epic, epicId, tickets) {
43
41
  `[epic-plan-decompose] Ticket #${epicId} is not a ${TYPE_LABELS.EPIC}.`,
44
42
  );
45
43
  }
46
- if (!epic.linkedIssues?.prd || !epic.linkedIssues?.techSpec) {
44
+ if (!hasTechSpecContent(epic.body ?? '')) {
47
45
  throw new Error(
48
- `[epic-plan-decompose] Epic #${epicId} is missing a linked PRD or Tech Spec. Run /epic-plan-spec first.`,
46
+ `[epic-plan-decompose] Epic #${epicId} body carries no Tech Spec sections (no ## Delivery Slicing). Run /plan Phase 7 first.`,
49
47
  );
50
48
  }
51
49
  if (!Array.isArray(tickets)) {
@@ -56,7 +54,7 @@ export function assertDecomposeInputs(epic, epicId, tickets) {
56
54
  }
57
55
 
58
56
  export function buildEpicSpecInput(epic, epicId) {
59
- const epicBody = ensurePlanningArtifacts(epic.body ?? '', epic.linkedIssues);
57
+ const epicBody = epic.body ?? '';
60
58
  const epicSpecInput = { id: epicId, title: epic.title };
61
59
  if (epicBody.length > 0) epicSpecInput.body = epicBody;
62
60
  return epicSpecInput;
@@ -117,8 +115,7 @@ export async function seedPlanState(provider, epicId, epic) {
117
115
  epicId,
118
116
  seed: {
119
117
  spec: {
120
- prdId: epic.linkedIssues.prd,
121
- techSpecId: epic.linkedIssues.techSpec,
118
+ techSpecPersisted: hasTechSpecContent(epic.body ?? ''),
122
119
  completedAt: null,
123
120
  },
124
121
  },
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * planning-artifacts.js — Phase 1 of the epic-plan-decompose pipeline
3
- * (Story #2466). Owns the `## Planning Artifacts` body shim and the
4
- * cross-Story conflict-policy resolver.
3
+ * (Story #2466). Owns the cross-Story conflict-policy resolver.
5
4
  *
6
- * Extracted verbatim from `epic-plan-decompose.js` so the named exports
7
- * the existing unit tests import (`ensurePlanningArtifacts`) keep their
8
- * public surface byte-identical.
5
+ * Story #4324 retired the `## Planning Artifacts` body shim
6
+ * (`ensurePlanningArtifacts`) with the context-ticket classes — the Epic
7
+ * body now carries the planning content itself as managed sections.
9
8
  *
10
9
  * @module lib/orchestration/epic-plan-decompose/phases/planning-artifacts
11
10
  */
@@ -13,39 +12,6 @@
13
12
  import { resolveListValue } from '../../../config/shared.js';
14
13
  import { DEFAULT_REGISTRY_PATTERNS } from '../../ticket-validator-conflicts.js';
15
14
 
16
- /**
17
- * Ensure the supplied Epic body carries a `## Planning Artifacts` section.
18
- * Idempotent — when the section already exists the body is returned
19
- * verbatim; when it's missing and `linkedIssues` carries resolved ids
20
- * the section is appended exactly once using the canonical
21
- * `- [ ] PRD: #N` / `Tech Spec: #N` / `Acceptance Spec: #N` lines that
22
- * `issue-link-parser.js` recognises (so cascade-close still resolves the
23
- * linked tickets).
24
- *
25
- * Story #2283.
26
- *
27
- * @param {string} body
28
- * @param {{ prd: number|null, techSpec: number|null, acceptanceSpec: number|null } | undefined | null} linkedIssues
29
- * @returns {string}
30
- */
31
- export function ensurePlanningArtifacts(body, linkedIssues) {
32
- const safeBody = typeof body === 'string' ? body : '';
33
- if (safeBody.includes('## Planning Artifacts')) return safeBody;
34
- if (!linkedIssues) return safeBody;
35
- const lines = [];
36
- if (Number.isInteger(linkedIssues.prd)) {
37
- lines.push(`- [ ] PRD: #${linkedIssues.prd}`);
38
- }
39
- if (Number.isInteger(linkedIssues.techSpec)) {
40
- lines.push(`- [ ] Tech Spec: #${linkedIssues.techSpec}`);
41
- }
42
- if (Number.isInteger(linkedIssues.acceptanceSpec)) {
43
- lines.push(`- [ ] Acceptance Spec: #${linkedIssues.acceptanceSpec}`);
44
- }
45
- if (lines.length === 0) return safeBody;
46
- return `${safeBody}\n\n## Planning Artifacts\n${lines.join('\n')}\n`;
47
- }
48
-
49
15
  /**
50
16
  * Resolve the cross-Story conflict-finding policy from `_config.planning`.
51
17
  * Both flags default to `false` so existing repos keep the advisory-only
@@ -28,9 +28,10 @@
28
28
  * operator passed `--force` (a deliberate
29
29
  * re-decompose that closes the old tree).
30
30
  *
31
- * The PRD / Tech Spec find-or-create idempotency already lives in
32
- * `phases/plan-epic.js` (keyed on `epic.linkedIssues`); these guards add the
33
- * cross-run mutual exclusion and the child-duplication refusal around it.
31
+ * The spec-persist idempotency already lives in `phases/plan-epic.js`
32
+ * (keyed on the Epic body's managed planning sections); these guards add
33
+ * the cross-run mutual exclusion and the child-duplication refusal
34
+ * around it.
34
35
  */
35
36
 
36
37
  import { getGitHub } from '../config/github.js';
@@ -341,12 +342,10 @@ export async function assertNoOpenPlanChildren({
341
342
  const openChildren = (children ?? []).filter((t) => {
342
343
  const labels = Array.isArray(t.labels) ? t.labels : [];
343
344
  const isOpen = t.state === undefined || t.state === 'open';
344
- // Context spec tickets (PRD / Tech Spec / Acceptance Spec) carry a
345
- // `context::*` label and since the `createTicket` factory injects
346
- // `type::story` by default also `type::story`. They are reference
347
- // artifacts that stay open across delivery, NOT plan children, so they
348
- // MUST be excluded here: counting them would make every first decompose
349
- // (where the three context tickets are the only open children) refuse.
345
+ // Legacy context spec tickets (the pre-#4324 Tech Spec / Acceptance
346
+ // Spec artifacts) carry a `context::*` label. Historical Epics keep
347
+ // them (forward-only cutover, no backfill), so they are still
348
+ // excluded here: they are reference artifacts, not plan children.
350
349
  const isContext = labels.some(
351
350
  (l) => typeof l === 'string' && l.startsWith('context::'),
352
351
  );
@@ -2,7 +2,7 @@
2
2
  * phases/authoring-context.js — emit-context phase.
3
3
  *
4
4
  * Builds the authoring context the host LLM (or the
5
- * `epic-plan-spec-author` Skill) needs to write the PRD and Tech Spec.
5
+ * `epic-plan-spec-author` Skill) needs to write the Tech Spec.
6
6
  * Returns a plain JSON-serialisable object; never hits the network beyond
7
7
  * the provider call needed to load the Epic.
8
8
  */
@@ -16,6 +16,7 @@ import {
16
16
  import { scanBddScenarios } from '../../../bdd-scenario-scanner.js';
17
17
  import { buildCodebaseSnapshot } from '../../../codebase-snapshot.js';
18
18
  import { getLimits, PROJECT_ROOT } from '../../../config-resolver.js';
19
+ import { hasEpicSection } from '../../../epic-body-sections.js';
19
20
  import { scanMemoryFreshness } from '../../../feedback-loop/memory-freshness.js';
20
21
  import { fetchPriorFeedback } from '../../../feedback-loop/prior-feedback-fetcher.js';
21
22
  import { Logger } from '../../../Logger.js';
@@ -24,7 +25,6 @@ import { applyBudget } from '../../planning-context-budget.js';
24
25
  import { collectReferences, hasNewFileCue } from '../../spec-freshness.js';
25
26
  import {
26
27
  ACCEPTANCE_SPEC_SYSTEM_PROMPT,
27
- PRD_SYSTEM_PROMPT,
28
28
  TECH_SPEC_SYSTEM_PROMPT,
29
29
  } from './prompts.js';
30
30
  import { buildAuthoringGrounding } from './spec-authoring-grounding.js';
@@ -59,7 +59,7 @@ export function resolveMemoryDir({ github } = {}) {
59
59
 
60
60
  /**
61
61
  * Build the authoring context the host LLM (or the
62
- * `epic-plan-spec-author` Skill) needs to write the PRD and Tech Spec.
62
+ * `epic-plan-spec-author` Skill) needs to write the Tech Spec.
63
63
  *
64
64
  * `docsContext` is bounded by the planning-context budget (Epic #817 Story 9):
65
65
  * over-budget payloads downgrade to a summary representation with headings +
@@ -205,12 +205,18 @@ export async function buildAuthoringContext(
205
205
  title: epic.title,
206
206
  body: epicBody.mode === 'full' ? epic.body : null,
207
207
  bodySummary: epicBody.mode === 'summary' ? epicBody.items[0] : null,
208
- linkedIssues: epic.linkedIssues ?? { prd: null, techSpec: null },
208
+ // Story #4324: the context-ticket classes are retired. A re-planned
209
+ // Epic's previous Tech Spec / Acceptance Table content rides along
210
+ // inside `body` (managed sections), which is how the author keeps
211
+ // AC IDs stable across re-plans.
212
+ planningSections: {
213
+ techSpec: hasEpicSection(epic.body ?? '', 'techSpec'),
214
+ acceptanceTable: hasEpicSection(epic.body ?? '', 'acceptanceTable'),
215
+ },
209
216
  },
210
217
  docsContext,
211
218
  codebaseSnapshot,
212
219
  systemPrompts: {
213
- prd: PRD_SYSTEM_PROMPT,
214
220
  techSpec: TECH_SPEC_SYSTEM_PROMPT,
215
221
  acceptanceSpec: ACCEPTANCE_SPEC_SYSTEM_PROMPT,
216
222
  },
@@ -4,25 +4,46 @@
4
4
  * Extracted from `epic-plan-spec.js` (refs #3685) so the top-level entry
5
5
  * stays a thin wire-up that scores at or above the maintainability floor;
6
6
  * the option schema and the epic-id guards live here.
7
+ *
8
+ * Story #4324: the separate-ticket persist flags `--techspec` /
9
+ * `--acceptance-spec` are retired with the context-ticket classes. The
10
+ * authored content now lands as sections of the Epic body; pass it via
11
+ * `--tech-spec <file>` and `--acceptance-table <file>`. The old flags fail
12
+ * with a usage error naming the removal.
7
13
  */
8
14
 
9
15
  import { parseArgs } from 'node:util';
10
16
 
17
+ /** Retired flags → the replacement guidance surfaced in the usage error. */
18
+ const RETIRED_FLAGS = Object.freeze({
19
+ '--techspec':
20
+ '--techspec was retired by the context-ticket fold (Story #4324): the Tech Spec is no longer a separate context::tech-spec ticket. Pass --tech-spec <file> — the content lands as a managed section of the Epic body.',
21
+ '--acceptance-spec':
22
+ '--acceptance-spec was retired by the context-ticket fold (Story #4324): the Acceptance Spec is no longer a separate context::acceptance-spec ticket. Pass --acceptance-table <file> — the AC-ID table lands as the ## Acceptance Table section of the Epic body.',
23
+ });
24
+
11
25
  /**
12
26
  * Parse and validate the `epic-plan-spec` CLI arguments.
13
27
  *
14
28
  * @param {string[]} [argv] Defaults to `process.argv.slice(2)`.
15
29
  * @returns {{ values: Record<string, unknown>, epicId: number }}
16
- * @throws {Error} when `--epic` is missing or not a number.
30
+ * @throws {Error} when `--epic` is missing or not a number, or when a
31
+ * retired separate-ticket persist flag is supplied.
17
32
  */
18
33
  export function parseEpicPlanSpecArgs(argv = process.argv.slice(2)) {
34
+ for (const arg of argv) {
35
+ const flag = arg.split('=')[0];
36
+ if (RETIRED_FLAGS[flag]) {
37
+ throw new Error(`[epic-plan-spec] ${RETIRED_FLAGS[flag]}`);
38
+ }
39
+ }
40
+
19
41
  const { values } = parseArgs({
20
42
  args: argv,
21
43
  options: {
22
44
  epic: { type: 'string' },
23
- prd: { type: 'string' },
24
- techspec: { type: 'string' },
25
- 'acceptance-spec': { type: 'string' },
45
+ 'tech-spec': { type: 'string' },
46
+ 'acceptance-table': { type: 'string' },
26
47
  'risk-verdict': { type: 'string' },
27
48
  force: { type: 'boolean', default: false },
28
49
  'force-review': { type: 'boolean', default: false },
@@ -35,7 +56,7 @@ export function parseEpicPlanSpecArgs(argv = process.argv.slice(2)) {
35
56
 
36
57
  if (!values.epic) {
37
58
  throw new Error(
38
- 'Usage: epic-plan-spec.js --epic <EpicId> (--emit-context [--pretty] [--full-context] | --prd <file> --techspec <file> --risk-verdict <file> [--acceptance-spec <file>]) [--force]',
59
+ 'Usage: epic-plan-spec.js --epic <EpicId> (--emit-context [--pretty] [--full-context] | --tech-spec <file> --risk-verdict <file> [--acceptance-table <file>]) [--force]',
39
60
  );
40
61
  }
41
62