@thiagodiogo/pscode 2.1.1 → 2.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.
Files changed (35) hide show
  1. package/dist/cli/index.js +3 -0
  2. package/dist/core/completions/command-registry.js +8 -0
  3. package/dist/core/config-prompts.d.ts +1 -1
  4. package/dist/core/config-prompts.js +8 -1
  5. package/dist/core/init.d.ts +3 -0
  6. package/dist/core/init.js +76 -3
  7. package/dist/core/pr-init-prompt.d.ts +13 -0
  8. package/dist/core/pr-init-prompt.js +76 -0
  9. package/dist/core/profile-sync-drift.js +2 -0
  10. package/dist/core/profiles.d.ts +3 -3
  11. package/dist/core/profiles.js +3 -2
  12. package/dist/core/project-config.d.ts +31 -0
  13. package/dist/core/project-config.js +24 -0
  14. package/dist/core/shared/skill-generation.js +5 -2
  15. package/dist/core/templates/skill-templates.d.ts +1 -0
  16. package/dist/core/templates/skill-templates.js +2 -0
  17. package/dist/core/templates/types.d.ts +0 -1
  18. package/dist/core/templates/workflows/apply-change.js +27 -3
  19. package/dist/core/templates/workflows/archive-change.js +0 -1
  20. package/dist/core/templates/workflows/bulk-archive-change.js +0 -1
  21. package/dist/core/templates/workflows/continue-change.js +0 -1
  22. package/dist/core/templates/workflows/explore.js +0 -1
  23. package/dist/core/templates/workflows/feedback.js +0 -1
  24. package/dist/core/templates/workflows/ff-change.js +0 -1
  25. package/dist/core/templates/workflows/handoff.d.ts +10 -0
  26. package/dist/core/templates/workflows/handoff.js +31 -0
  27. package/dist/core/templates/workflows/new-change.js +0 -1
  28. package/dist/core/templates/workflows/onboard.js +0 -1
  29. package/dist/core/templates/workflows/propose.js +45 -31
  30. package/dist/core/templates/workflows/trello-draft.js +4 -16
  31. package/dist/core/templates/workflows/trello-next-step-comment.d.ts +22 -15
  32. package/dist/core/templates/workflows/trello-next-step-comment.js +69 -31
  33. package/dist/core/templates/workflows/trello-setup.js +0 -1
  34. package/dist/core/templates/workflows/verify-change.js +0 -1
  35. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -76,6 +76,8 @@ program
76
76
  .option('--tools <tools>', toolsOptionDescription)
77
77
  .option('--force', 'Auto-cleanup legacy files without prompting')
78
78
  .option('--profile <profile>', 'Workflow profile to use (core, full, trello)')
79
+ .option('--pr', 'Enable PR workflow config without interactive prompts (uses defaults: branch=feat/{change-name}, title=[{type}] {change-name})')
80
+ .option('--no-pr', 'Disable PR workflow config without interactive prompts')
79
81
  .action(async (targetPath = '.', options) => {
80
82
  try {
81
83
  // Validate that the path is a valid directory
@@ -103,6 +105,7 @@ program
103
105
  tools: options?.tools,
104
106
  force: options?.force,
105
107
  profile: options?.profile,
108
+ pr: options?.pr,
106
109
  });
107
110
  await initCommand.execute(targetPath);
108
111
  }
@@ -22,6 +22,14 @@ export const COMMAND_REGISTRY = [
22
22
  takesValue: true,
23
23
  values: ['standard', 'dixi'],
24
24
  },
25
+ {
26
+ name: 'pr',
27
+ description: 'Enable PR workflow config without interactive prompts',
28
+ },
29
+ {
30
+ name: 'no-pr',
31
+ description: 'Disable PR workflow config without interactive prompts',
32
+ },
25
33
  ],
26
34
  },
27
35
  {
@@ -2,7 +2,7 @@ import type { ProjectConfig } from './project-config.js';
2
2
  /**
3
3
  * Serialize config to YAML string with helpful comments.
4
4
  *
5
- * @param config - Partial config object (schema required, context/rules optional)
5
+ * @param config - Partial config object (schema required, context/rules/pr optional)
6
6
  * @returns YAML string ready to write to file
7
7
  */
8
8
  export declare function serializeConfig(config: Partial<ProjectConfig>): string;
@@ -1,7 +1,8 @@
1
+ import { stringify as stringifyYaml } from 'yaml';
1
2
  /**
2
3
  * Serialize config to YAML string with helpful comments.
3
4
  *
4
- * @param config - Partial config object (schema required, context/rules optional)
5
+ * @param config - Partial config object (schema required, context/rules/pr optional)
5
6
  * @returns YAML string ready to write to file
6
7
  */
7
8
  export function serializeConfig(config) {
@@ -9,6 +10,12 @@ export function serializeConfig(config) {
9
10
  // Schema (required)
10
11
  lines.push(`schema: ${config.schema}`);
11
12
  lines.push('');
13
+ // PR workflow config (if present)
14
+ if (config.pr !== undefined) {
15
+ const prYaml = stringifyYaml({ pr: config.pr }, { lineWidth: 0 });
16
+ lines.push(prYaml.trimEnd());
17
+ lines.push('');
18
+ }
12
19
  // Context section with comments
13
20
  lines.push('# Project context (optional)');
14
21
  lines.push('# This is shown to AI when creating artifacts.');
@@ -9,12 +9,14 @@ type InitCommandOptions = {
9
9
  force?: boolean;
10
10
  interactive?: boolean;
11
11
  profile?: string;
12
+ pr?: boolean;
12
13
  };
13
14
  export declare class InitCommand {
14
15
  private readonly toolsArg?;
15
16
  private readonly force;
16
17
  private readonly interactiveOption?;
17
18
  private readonly profileOverride?;
19
+ private readonly prFlag?;
18
20
  constructor(options?: InitCommandOptions);
19
21
  execute(targetPath: string): Promise<void>;
20
22
  private validate;
@@ -28,6 +30,7 @@ export declare class InitCommand {
28
30
  private resolveToolsArg;
29
31
  private validateTools;
30
32
  private handleTrelloSetup;
33
+ private handlePrSetup;
31
34
  private createDirectoryStructure;
32
35
  private generateSkillsAndCommands;
33
36
  private createConfig;
package/dist/core/init.js CHANGED
@@ -24,8 +24,10 @@ import { getGlobalConfig } from './global-config.js';
24
24
  import { getProfileWorkflows, isValidProfile, DEFAULT_PROFILE, PROFILES, ALL_WORKFLOWS } from './profiles.js';
25
25
  import { detectDixiStack, getDixiStackFamily, getDixiStackLabel, installDixiExtras } from './presets/dixi.js';
26
26
  import { stringify as stringifyYaml } from 'yaml';
27
+ import { parse as parseYaml } from 'yaml';
27
28
  import { getAvailableTools } from './available-tools.js';
28
29
  import { migrateIfNeeded } from './migration.js';
30
+ import { runPrInitPrompt } from './pr-init-prompt.js';
29
31
  const require = createRequire(import.meta.url);
30
32
  const { version: PSCODE_VERSION } = require('../../package.json');
31
33
  // -----------------------------------------------------------------------------
@@ -50,6 +52,8 @@ const WORKFLOW_TO_SKILL_DIR = {
50
52
  // Trello-specific workflows
51
53
  'trello-setup': 'pscode-trello-setup',
52
54
  'draft': 'pscode-trello-draft',
55
+ // Productivity workflows
56
+ 'handoff': 'pscode-handoff',
53
57
  // Dixi-specific workflows
54
58
  'rfc': 'pscode-dixi-rfc',
55
59
  'design': 'pscode-dixi-design',
@@ -67,11 +71,13 @@ export class InitCommand {
67
71
  force;
68
72
  interactiveOption;
69
73
  profileOverride;
74
+ prFlag;
70
75
  constructor(options = {}) {
71
76
  this.toolsArg = options.tools;
72
77
  this.force = options.force ?? false;
73
78
  this.interactiveOption = options.interactive;
74
79
  this.profileOverride = options.profile;
80
+ this.prFlag = options.pr;
75
81
  }
76
82
  async execute(targetPath) {
77
83
  const projectPath = path.resolve(targetPath);
@@ -108,6 +114,8 @@ export class InitCommand {
108
114
  await this.createDirectoryStructure(pscodePath, extendMode);
109
115
  // Trello integration setup (interactive mode only)
110
116
  const trelloConfigured = await this.handleTrelloSetup(pscodePath);
117
+ // PR workflow setup (interactive or via flags)
118
+ const prConfig = await this.handlePrSetup(pscodePath, extendMode);
111
119
  // Generate skills and commands for each tool
112
120
  const results = await this.generateSkillsAndCommands(projectPath, validatedTools);
113
121
  // Dixi profile extras: stack detection and .pscode-dixi.yaml
@@ -118,7 +126,7 @@ export class InitCommand {
118
126
  await this.generateJiraFiles(projectPath);
119
127
  }
120
128
  // Create config.yaml if needed
121
- const configStatus = await this.createConfig(pscodePath, extendMode);
129
+ const configStatus = await this.createConfig(pscodePath, extendMode, prConfig ?? undefined);
122
130
  // Display success message
123
131
  this.displaySuccessMessage(projectPath, validatedTools, results, configStatus, trelloConfigured);
124
132
  }
@@ -405,6 +413,51 @@ export class InitCommand {
405
413
  }
406
414
  }
407
415
  // ═══════════════════════════════════════════════════════════
416
+ // PR WORKFLOW SETUP
417
+ // ═══════════════════════════════════════════════════════════
418
+ async handlePrSetup(pscodePath, extendMode) {
419
+ // --pr flag: enable PR without prompts (default values)
420
+ if (this.prFlag === true) {
421
+ return {
422
+ enabled: true,
423
+ branch: { pattern: 'feat/{change-name}' },
424
+ title: { template: '[{type}] {change-name}' },
425
+ description: { template: '## O que foi feito\n\n\n## Como testar\n\n\n## Referências\n' },
426
+ comments: { linkInTask: true },
427
+ };
428
+ }
429
+ // --no-pr flag: disable PR without prompts
430
+ if (this.prFlag === false) {
431
+ return { enabled: false };
432
+ }
433
+ // Non-interactive mode with no flag: skip PR config
434
+ if (!this.canPromptInteractively()) {
435
+ return null;
436
+ }
437
+ // Interactive mode: if config exists, ask if user wants to reconfigure PR
438
+ if (extendMode) {
439
+ const configPath = path.join(pscodePath, 'config.yaml');
440
+ const configYmlPath = path.join(pscodePath, 'config.yml');
441
+ const configExists = fs.existsSync(configPath) || fs.existsSync(configYmlPath);
442
+ if (configExists) {
443
+ const { confirm } = await import('@inquirer/prompts');
444
+ const wantsReconfigure = await confirm({
445
+ message: 'Reconfigurar preferências de PR?',
446
+ default: false,
447
+ });
448
+ if (!wantsReconfigure) {
449
+ return null; // Preserve existing PR config
450
+ }
451
+ }
452
+ }
453
+ try {
454
+ return await runPrInitPrompt();
455
+ }
456
+ catch {
457
+ return null;
458
+ }
459
+ }
460
+ // ═══════════════════════════════════════════════════════════
408
461
  // DIRECTORY STRUCTURE
409
462
  // ═══════════════════════════════════════════════════════════
410
463
  async createDirectoryStructure(pscodePath, extendMode) {
@@ -522,19 +575,36 @@ export class InitCommand {
522
575
  // ═══════════════════════════════════════════════════════════
523
576
  // CONFIG FILE
524
577
  // ═══════════════════════════════════════════════════════════
525
- async createConfig(pscodePath, extendMode) {
578
+ async createConfig(pscodePath, extendMode, prConfig) {
526
579
  const configPath = path.join(pscodePath, 'config.yaml');
527
580
  const configYmlPath = path.join(pscodePath, 'config.yml');
528
581
  const configYamlExists = fs.existsSync(configPath);
529
582
  const configYmlExists = fs.existsSync(configYmlPath);
530
583
  if (configYamlExists || configYmlExists) {
584
+ // If PR config was provided, merge it into the existing file
585
+ if (prConfig !== undefined) {
586
+ try {
587
+ const existingPath = configYamlExists ? configPath : configYmlPath;
588
+ const raw = parseYaml(fs.readFileSync(existingPath, 'utf-8'));
589
+ const existingSchema = (raw && typeof raw.schema === 'string') ? raw.schema : DEFAULT_SCHEMA;
590
+ const globalConfig = getGlobalConfig();
591
+ const resolvedProfile = this.resolveProfileOverride() ?? (isValidProfile(globalConfig.profile ?? '') ? globalConfig.profile : DEFAULT_PROFILE);
592
+ const schema = existingSchema || (resolvedProfile === 'dixi' ? 'pstld-workflow' : DEFAULT_SCHEMA);
593
+ const yamlContent = serializeConfig({ schema, pr: prConfig });
594
+ await FileSystemUtils.writeFile(configPath, yamlContent);
595
+ return 'updated';
596
+ }
597
+ catch {
598
+ return 'exists';
599
+ }
600
+ }
531
601
  return 'exists';
532
602
  }
533
603
  try {
534
604
  const globalConfig = getGlobalConfig();
535
605
  const resolvedProfile = this.resolveProfileOverride() ?? (isValidProfile(globalConfig.profile ?? '') ? globalConfig.profile : DEFAULT_PROFILE);
536
606
  const schema = resolvedProfile === 'dixi' ? 'pstld-workflow' : DEFAULT_SCHEMA;
537
- const yamlContent = serializeConfig({ schema });
607
+ const yamlContent = serializeConfig({ schema, pr: prConfig });
538
608
  await FileSystemUtils.writeFile(configPath, yamlContent);
539
609
  return 'created';
540
610
  }
@@ -597,6 +667,9 @@ export class InitCommand {
597
667
  const createdSchema = profileForSchema === 'dixi' ? 'pstld-workflow' : DEFAULT_SCHEMA;
598
668
  console.log(`Config: pscode/config.yaml (schema: ${createdSchema})`);
599
669
  }
670
+ else if (configStatus === 'updated') {
671
+ console.log(`Config: pscode/config.yaml (updated with PR config)`);
672
+ }
600
673
  else if (configStatus === 'exists') {
601
674
  // Show actual filename (config.yaml or config.yml)
602
675
  const configYaml = path.join(projectPath, PSCODE_DIR_NAME, 'config.yaml');
@@ -0,0 +1,13 @@
1
+ /**
2
+ * PR Init Prompt
3
+ *
4
+ * Handles the interactive PR workflow configuration questions during `pscode init`.
5
+ * Collects branch pattern, PR title/description templates, and tracker comment settings.
6
+ */
7
+ import type { PrConfig } from './project-config.js';
8
+ /**
9
+ * Runs the interactive PR configuration questions during `pscode init`.
10
+ * Returns the collected PrConfig, or null if the user opts out.
11
+ */
12
+ export declare function runPrInitPrompt(): Promise<PrConfig | null>;
13
+ //# sourceMappingURL=pr-init-prompt.d.ts.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * PR Init Prompt
3
+ *
4
+ * Handles the interactive PR workflow configuration questions during `pscode init`.
5
+ * Collects branch pattern, PR title/description templates, and tracker comment settings.
6
+ */
7
+ import chalk from 'chalk';
8
+ const DEFAULT_BRANCH_PATTERN = 'feat/{change-name}';
9
+ const DEFAULT_TITLE_TEMPLATE = '[{type}] {change-name}';
10
+ const DEFAULT_DESCRIPTION_TEMPLATE = '## O que foi feito\n\n\n## Como testar\n\n\n## Referências\n';
11
+ /**
12
+ * Runs the interactive PR configuration questions during `pscode init`.
13
+ * Returns the collected PrConfig, or null if the user opts out.
14
+ */
15
+ export async function runPrInitPrompt() {
16
+ const { confirm, input } = await import('@inquirer/prompts');
17
+ console.log();
18
+ console.log(chalk.bold('Workflow de PR'));
19
+ console.log(chalk.dim(' Configure padrões de branch, título e descrição para Pull Requests.'));
20
+ console.log(chalk.dim(' Template variables: {change-name}, {type}, {ticket}'));
21
+ console.log();
22
+ const wantsPr = await confirm({
23
+ message: 'Usar workflow de PR neste projeto? (branch dedicada + PR obrigatório)',
24
+ default: false,
25
+ });
26
+ if (!wantsPr) {
27
+ return { enabled: false };
28
+ }
29
+ const branchPattern = await input({
30
+ message: 'Padrão de nome de branch:',
31
+ default: DEFAULT_BRANCH_PATTERN,
32
+ });
33
+ const titleTemplate = await input({
34
+ message: 'Template de título do PR:',
35
+ default: DEFAULT_TITLE_TEMPLATE,
36
+ });
37
+ const useDefaultDescription = await confirm({
38
+ message: 'Usar template padrão de descrição de PR?',
39
+ default: true,
40
+ });
41
+ let descriptionTemplate;
42
+ if (useDefaultDescription) {
43
+ descriptionTemplate = DEFAULT_DESCRIPTION_TEMPLATE;
44
+ }
45
+ else {
46
+ const customDesc = await input({
47
+ message: 'Caminho para arquivo .md com template de descrição (ou deixe vazio para usar o padrão):',
48
+ default: '',
49
+ });
50
+ if (customDesc.trim()) {
51
+ try {
52
+ const { readFileSync } = await import('fs');
53
+ descriptionTemplate = readFileSync(customDesc.trim(), 'utf-8');
54
+ }
55
+ catch {
56
+ console.log(chalk.yellow(` Arquivo não encontrado — usando template padrão.`));
57
+ descriptionTemplate = DEFAULT_DESCRIPTION_TEMPLATE;
58
+ }
59
+ }
60
+ else {
61
+ descriptionTemplate = DEFAULT_DESCRIPTION_TEMPLATE;
62
+ }
63
+ }
64
+ const linkInTask = await confirm({
65
+ message: 'Comentar link do PR na tarefa (Trello/Jira)?',
66
+ default: true,
67
+ });
68
+ return {
69
+ enabled: true,
70
+ branch: { pattern: branchPattern.trim() || DEFAULT_BRANCH_PATTERN },
71
+ title: { template: titleTemplate.trim() || DEFAULT_TITLE_TEMPLATE },
72
+ description: { template: descriptionTemplate },
73
+ comments: { linkInTask },
74
+ };
75
+ }
76
+ //# sourceMappingURL=pr-init-prompt.js.map
@@ -21,6 +21,8 @@ export const WORKFLOW_TO_SKILL_DIR = {
21
21
  // Trello-specific workflows
22
22
  'trello-setup': 'pscode-trello-setup',
23
23
  'draft': 'pscode-trello-draft',
24
+ // Productivity workflows
25
+ 'handoff': 'pscode-handoff',
24
26
  // Dixi-specific workflows
25
27
  'rfc': 'pscode-dixi-rfc',
26
28
  'design': 'pscode-dixi-design',
@@ -5,7 +5,7 @@
5
5
  * `pscode init --profile <name>` or `pscode config profile <name>`.
6
6
  * The workflow lists are fixed in code — users cannot customise them.
7
7
  */
8
- export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "complete", "bulk-archive", "verify", "onboard", "trello-setup", "draft", "rfc", "design", "tasks", "arch-check", "adr", "jira-sync", "dod"];
8
+ export declare const ALL_WORKFLOWS: readonly ["propose", "explore", "new", "continue", "apply", "ff", "complete", "bulk-archive", "verify", "onboard", "trello-setup", "draft", "rfc", "design", "tasks", "arch-check", "adr", "jira-sync", "dod", "handoff"];
9
9
  export type WorkflowId = (typeof ALL_WORKFLOWS)[number];
10
10
  export interface ProfileDefinition {
11
11
  description: string;
@@ -14,11 +14,11 @@ export interface ProfileDefinition {
14
14
  export declare const PROFILES: {
15
15
  readonly standard: {
16
16
  readonly description: "Padrão — propose, explore, apply, complete";
17
- readonly workflows: readonly ["propose", "explore", "apply", "complete", "trello-setup", "draft"];
17
+ readonly workflows: readonly ["propose", "explore", "apply", "complete", "trello-setup", "draft", "handoff"];
18
18
  };
19
19
  readonly dixi: {
20
20
  readonly description: "Dixi — propose, explore, apply, complete com guardrails para Java/Spring e React/Next.js";
21
- readonly workflows: readonly ["propose", "explore", "apply", "complete", "trello-setup", "draft"];
21
+ readonly workflows: readonly ["propose", "explore", "apply", "complete", "trello-setup", "draft", "handoff"];
22
22
  };
23
23
  };
24
24
  export type ProfileName = keyof typeof PROFILES;
@@ -25,15 +25,16 @@ export const ALL_WORKFLOWS = [
25
25
  'adr',
26
26
  'jira-sync',
27
27
  'dod',
28
+ 'handoff',
28
29
  ];
29
30
  export const PROFILES = {
30
31
  standard: {
31
32
  description: 'Padrão — propose, explore, apply, complete',
32
- workflows: ['propose', 'explore', 'apply', 'complete', 'trello-setup', 'draft'],
33
+ workflows: ['propose', 'explore', 'apply', 'complete', 'trello-setup', 'draft', 'handoff'],
33
34
  },
34
35
  dixi: {
35
36
  description: 'Dixi — propose, explore, apply, complete com guardrails para Java/Spring e React/Next.js',
36
- workflows: ['propose', 'explore', 'apply', 'complete', 'trello-setup', 'draft'],
37
+ workflows: ['propose', 'explore', 'apply', 'complete', 'trello-setup', 'draft', 'handoff'],
37
38
  },
38
39
  };
39
40
  export const DEFAULT_PROFILE = 'standard';
@@ -1,4 +1,20 @@
1
1
  import { z } from 'zod';
2
+ export declare const PrConfigSchema: z.ZodObject<{
3
+ enabled: z.ZodBoolean;
4
+ branch: z.ZodOptional<z.ZodObject<{
5
+ pattern: z.ZodString;
6
+ }, z.core.$strip>>;
7
+ title: z.ZodOptional<z.ZodObject<{
8
+ template: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ description: z.ZodOptional<z.ZodObject<{
11
+ template: z.ZodString;
12
+ }, z.core.$strip>>;
13
+ comments: z.ZodOptional<z.ZodObject<{
14
+ linkInTask: z.ZodBoolean;
15
+ }, z.core.$strip>>;
16
+ }, z.core.$strip>;
17
+ export type PrConfig = z.infer<typeof PrConfigSchema>;
2
18
  /**
3
19
  * Zod schema for project configuration.
4
20
  *
@@ -16,6 +32,21 @@ export declare const ProjectConfigSchema: z.ZodObject<{
16
32
  schema: z.ZodString;
17
33
  context: z.ZodOptional<z.ZodString>;
18
34
  rules: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
35
+ pr: z.ZodOptional<z.ZodObject<{
36
+ enabled: z.ZodBoolean;
37
+ branch: z.ZodOptional<z.ZodObject<{
38
+ pattern: z.ZodString;
39
+ }, z.core.$strip>>;
40
+ title: z.ZodOptional<z.ZodObject<{
41
+ template: z.ZodString;
42
+ }, z.core.$strip>>;
43
+ description: z.ZodOptional<z.ZodObject<{
44
+ template: z.ZodString;
45
+ }, z.core.$strip>>;
46
+ comments: z.ZodOptional<z.ZodObject<{
47
+ linkInTask: z.ZodBoolean;
48
+ }, z.core.$strip>>;
49
+ }, z.core.$strip>>;
19
50
  }, z.core.$strip>;
20
51
  export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;
21
52
  /**
@@ -2,6 +2,13 @@ import { existsSync, readFileSync } from 'fs';
2
2
  import path from 'path';
3
3
  import { parse as parseYaml } from 'yaml';
4
4
  import { z } from 'zod';
5
+ export const PrConfigSchema = z.object({
6
+ enabled: z.boolean(),
7
+ branch: z.object({ pattern: z.string() }).optional(),
8
+ title: z.object({ template: z.string() }).optional(),
9
+ description: z.object({ template: z.string() }).optional(),
10
+ comments: z.object({ linkInTask: z.boolean() }).optional(),
11
+ });
5
12
  /**
6
13
  * Zod schema for project configuration.
7
14
  *
@@ -34,6 +41,8 @@ export const ProjectConfigSchema = z.object({
34
41
  )
35
42
  .optional()
36
43
  .describe('Per-artifact rules, keyed by artifact ID'),
44
+ // Optional: PR workflow configuration
45
+ pr: PrConfigSchema.optional().describe('PR workflow configuration'),
37
46
  });
38
47
  const MAX_CONTEXT_SIZE = 50 * 1024; // 50KB hard limit
39
48
  /**
@@ -99,6 +108,21 @@ export function readProjectConfig(projectRoot) {
99
108
  console.warn(`Invalid 'context' field in config (must be string)`);
100
109
  }
101
110
  }
111
+ // Parse pr field using Zod safeParse
112
+ if (raw.pr !== undefined) {
113
+ if (typeof raw.pr === 'object' && raw.pr !== null) {
114
+ const prResult = PrConfigSchema.safeParse(raw.pr);
115
+ if (prResult.success) {
116
+ config.pr = prResult.data;
117
+ }
118
+ else {
119
+ console.warn(`Invalid 'pr' field in config: ${prResult.error.issues.map((i) => i.message).join(', ')}`);
120
+ }
121
+ }
122
+ else {
123
+ console.warn(`Invalid 'pr' field in config (must be object)`);
124
+ }
125
+ }
102
126
  // Parse rules field using Zod
103
127
  if (raw.rules !== undefined) {
104
128
  const rulesField = z.record(z.string(), z.array(z.string()));
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Shared utilities for generating skill and command files.
5
5
  */
6
- import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getCompleteChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getProposeSkillTemplate, getTrelloSetupSkillTemplate, getTrelloDraftSkillTemplate, getPsExploreCommandTemplate, getPsNewCommandTemplate, getPsContinueCommandTemplate, getPsApplyCommandTemplate, getPsFfCommandTemplate, getPsCompleteCommandTemplate, getPsBulkArchiveCommandTemplate, getPsVerifyCommandTemplate, getPsOnboardCommandTemplate, getPsProposeCommandTemplate, getTrelloSetupCommandTemplate, getTrelloDraftCommandTemplate, } from '../templates/skill-templates.js';
6
+ import { getExploreSkillTemplate, getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getCompleteChangeSkillTemplate, getBulkArchiveChangeSkillTemplate, getVerifyChangeSkillTemplate, getOnboardSkillTemplate, getProposeSkillTemplate, getTrelloSetupSkillTemplate, getTrelloDraftSkillTemplate, getHandoffSkillTemplate, getPsExploreCommandTemplate, getPsNewCommandTemplate, getPsContinueCommandTemplate, getPsApplyCommandTemplate, getPsFfCommandTemplate, getPsCompleteCommandTemplate, getPsBulkArchiveCommandTemplate, getPsVerifyCommandTemplate, getPsOnboardCommandTemplate, getPsProposeCommandTemplate, getTrelloSetupCommandTemplate, getTrelloDraftCommandTemplate, getHandoffCommandTemplate, } from '../templates/skill-templates.js';
7
7
  /**
8
8
  * Gets skill templates with their directory names, optionally filtered by workflow IDs.
9
9
  *
@@ -24,6 +24,8 @@ export function getSkillTemplates(workflowFilter) {
24
24
  // Trello-specific workflows
25
25
  { template: getTrelloSetupSkillTemplate(), dirName: 'pscode-trello-setup', workflowId: 'trello-setup' },
26
26
  { template: getTrelloDraftSkillTemplate(), dirName: 'pscode-trello-draft', workflowId: 'draft' },
27
+ // Productivity workflows
28
+ { template: getHandoffSkillTemplate(), dirName: 'pscode-handoff', workflowId: 'handoff' },
27
29
  ];
28
30
  if (!workflowFilter)
29
31
  return all;
@@ -50,6 +52,8 @@ export function getCommandTemplates(workflowFilter) {
50
52
  // Trello-specific workflows
51
53
  { template: getTrelloSetupCommandTemplate(), id: 'trello-setup' },
52
54
  { template: getTrelloDraftCommandTemplate(), id: 'draft' },
55
+ // Productivity workflows
56
+ { template: getHandoffCommandTemplate(), id: 'handoff' },
53
57
  ];
54
58
  if (!workflowFilter)
55
59
  return all;
@@ -86,7 +90,6 @@ export function generateSkillContent(template, generatedByVersion, transformInst
86
90
  return `---
87
91
  name: ${template.name}
88
92
  description: ${template.description}
89
- license: ${template.license || 'MIT'}
90
93
  compatibility: ${template.compatibility || 'Requires pscode CLI.'}
91
94
  metadata:
92
95
  author: ${template.metadata?.author || 'pscode'}
@@ -17,4 +17,5 @@ export { getProposeSkillTemplate, getPsProposeCommandTemplate } from './workflow
17
17
  export { getFeedbackSkillTemplate } from './workflows/feedback.js';
18
18
  export { getTrelloSetupSkillTemplate, getTrelloSetupCommandTemplate } from './workflows/trello-setup.js';
19
19
  export { getTrelloDraftSkillTemplate, getTrelloDraftCommandTemplate } from './workflows/trello-draft.js';
20
+ export { getHandoffSkillTemplate, getHandoffCommandTemplate } from './workflows/handoff.js';
20
21
  //# sourceMappingURL=skill-templates.d.ts.map
@@ -17,4 +17,6 @@ export { getFeedbackSkillTemplate } from './workflows/feedback.js';
17
17
  // Trello-specific workflows
18
18
  export { getTrelloSetupSkillTemplate, getTrelloSetupCommandTemplate } from './workflows/trello-setup.js';
19
19
  export { getTrelloDraftSkillTemplate, getTrelloDraftCommandTemplate } from './workflows/trello-draft.js';
20
+ // Productivity workflows
21
+ export { getHandoffSkillTemplate, getHandoffCommandTemplate } from './workflows/handoff.js';
20
22
  //# sourceMappingURL=skill-templates.js.map
@@ -5,7 +5,6 @@ export interface SkillTemplate {
5
5
  name: string;
6
6
  description: string;
7
7
  instructions: string;
8
- license?: string;
9
8
  compatibility?: string;
10
9
  metadata?: Record<string, string>;
11
10
  }
@@ -1,9 +1,9 @@
1
+ import { buildNextStepComment } from './trello-next-step-comment.js';
1
2
  export function getApplyChangeSkillTemplate() {
2
3
  return {
3
4
  name: 'pscode-apply-change',
4
5
  description: 'Implement tasks from an Pscode change. Use when the user wants to start implementing, continue implementation, or work through tasks.',
5
6
  instructions: getApplyInstructions(),
6
- license: 'MIT',
7
7
  compatibility: 'Requires pscode CLI.',
8
8
  metadata: { author: 'pscode', version: '1.0' },
9
9
  };
@@ -18,6 +18,13 @@ export function getPsApplyCommandTemplate() {
18
18
  };
19
19
  }
20
20
  function getApplyInstructions() {
21
+ // Pre-filled next-step comment (card title interpolated), shared via the
22
+ // trello-next-step-comment utility and indented to sit inside the Trello
23
+ // comment `text: |` block in the testing phase below.
24
+ const completeNextStep = buildNextStepComment('<card title>', '/ps:complete')
25
+ .split('\n')
26
+ .map((line) => (line.length > 0 ? ` ${line}` : ''))
27
+ .join('\n');
21
28
  return `Implement tasks from a Pscode change.
22
29
 
23
30
  **Input**: Optionally specify a change name (e.g., \`/ps:apply add-auth\`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
@@ -94,10 +101,27 @@ function getApplyInstructions() {
94
101
 
95
102
  **Workspace guard:** If status JSON reports \`actionContext.mode: "workspace-planning"\` and \`allowedEditRoots\` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area, and STOP before editing files.
96
103
 
97
- 5. **Read context files**
104
+ 5. **Read context files and PR config**
98
105
 
99
106
  Read every file path listed under \`contextFiles\` from the apply instructions output.
100
107
 
108
+ Additionally, use the **Read tool** to read \`pscode/config.yaml\` from the current working directory.
109
+
110
+ **If \`pscode/config.yaml\` exists and \`pr.enabled: true\`:**
111
+
112
+ Before starting any implementation, inform the user of the PR workflow requirements:
113
+
114
+ > 🔀 **Workflow de PR ativo** — este projeto requer branches dedicadas e Pull Requests.
115
+ > - Branch: crie uma branch com o padrão \`<pr.branch.pattern>\` antes de codificar
116
+ > - Título do PR: \`<pr.title.template>\`
117
+ > - Descrição do PR: use o template definido em \`pr.description.template\`
118
+ > - Ao abrir o PR: \`<"comente o link do PR nesta task" se pr.comments.linkInTask: true, senão omita>\`
119
+
120
+ The agent MUST create the branch with the configured pattern before making any code changes.
121
+ Template variables available: \`{change-name}\` = current change name, \`{type}\` = feat/fix/chore, \`{ticket}\` = ticket ID if available.
122
+
123
+ **If \`pscode/config.yaml\` does not exist, or \`pr.enabled: false\`, or file not found:** continue normally without any PR instructions.
124
+
101
125
  6. **Show current progress**
102
126
 
103
127
  Display:
@@ -182,7 +206,7 @@ function getApplyInstructions() {
182
206
  Testado por: <usuario / Claude>
183
207
  Status: Funcionando
184
208
 
185
- Proximo passo: /ps:complete <name> para arquivar a change.
209
+ ${completeNextStep}
186
210
  \`\`\`
187
211
 
188
212
  If any Trello call fails, continue — Trello is auxiliary, never blocking.
@@ -3,7 +3,6 @@ export function getCompleteChangeSkillTemplate() {
3
3
  name: 'pscode-archive-change',
4
4
  description: 'Complete a completed change. Use when the user wants to finalize and complete a change after implementation is complete.',
5
5
  instructions: getArchiveInstructions(),
6
- license: 'MIT',
7
6
  compatibility: 'Requires pscode CLI.',
8
7
  metadata: { author: 'pscode', version: '1.0' },
9
8
  };
@@ -239,7 +239,6 @@ No active changes found. Create a new change to get started.
239
239
  - Preserve .pscode.yaml when moving to archive
240
240
  - Archive directory target uses current date: YYYY-MM-DD-<name>
241
241
  - If archive target exists, fail that change but continue with others`,
242
- license: 'MIT',
243
242
  compatibility: 'Requires pscode CLI.',
244
243
  metadata: { author: 'pscode', version: '1.0' },
245
244
  };
@@ -110,7 +110,6 @@ For other schemas, follow the \`instruction\` field from the CLI output.
110
110
  - **IMPORTANT**: \`context\` and \`rules\` are constraints for YOU, not content for the file
111
111
  - Do NOT copy \`<context>\`, \`<rules>\`, \`<project_context>\` blocks into the artifact
112
112
  - These guide what you write, but should never appear in the output`,
113
- license: 'MIT',
114
113
  compatibility: 'Requires pscode CLI.',
115
114
  metadata: { author: 'pscode', version: '1.0' },
116
115
  };
@@ -426,7 +426,6 @@ Do NOT move the card. End the loop.
426
426
  - **Do question assumptions** - Including the user's and your own
427
427
  - **Always run the refinement loop after propose** - When exploration leads to a proposal, the refinement validation loop (Steps RF1–RF3) is mandatory, not optional
428
428
  - **Preserve the loop** - Do not exit until the user explicitly approves or cancels`,
429
- license: 'MIT',
430
429
  compatibility: 'Requires pscode CLI.',
431
430
  metadata: { author: 'pscode', version: '1.0' },
432
431
  };
@@ -100,7 +100,6 @@ Does this look good? I can modify it if you'd like, or submit it as-is.
100
100
  \`\`\`
101
101
 
102
102
  Only proceed with submission after user confirms.`,
103
- license: 'MIT',
104
103
  compatibility: 'Requires pscode CLI.',
105
104
  metadata: { author: 'pscode', version: '1.0' },
106
105
  };
@@ -93,7 +93,6 @@ After completing all artifacts, summarize:
93
93
  - If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
94
94
  - If a change with that name already exists, suggest continuing that change instead
95
95
  - Verify each artifact file exists after writing before proceeding to next`,
96
- license: 'MIT',
97
96
  compatibility: 'Requires pscode CLI.',
98
97
  metadata: { author: 'pscode', version: '1.0' },
99
98
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Handoff Skill / Command Template
3
+ *
4
+ * Compacts the current conversation into a handoff document so a fresh agent
5
+ * (or the same user in a new session) can continue the work seamlessly.
6
+ */
7
+ import type { SkillTemplate, CommandTemplate } from '../types.js';
8
+ export declare function getHandoffSkillTemplate(): SkillTemplate;
9
+ export declare function getHandoffCommandTemplate(): CommandTemplate;
10
+ //# sourceMappingURL=handoff.d.ts.map
@@ -0,0 +1,31 @@
1
+ export function getHandoffSkillTemplate() {
2
+ return {
3
+ name: 'pscode-handoff',
4
+ description: 'Compact the current conversation into a handoff document for another agent to pick up. Use when the user wants to hand off work to a new session or agent.',
5
+ instructions: getHandoffInstructions(),
6
+ compatibility: 'Works with any pscode project.',
7
+ metadata: { author: 'pscode', version: '1.0' },
8
+ };
9
+ }
10
+ function getHandoffInstructions() {
11
+ return `Write a handoff document summarising the current conversation so a fresh agent can continue the work. Save to the temporary directory of the user's OS — not the current workspace.
12
+
13
+ Include a "suggested skills" section in the document, which suggests skills that the agent should invoke.
14
+
15
+ Do not duplicate content already captured in other artifacts (PRDs, plans, ADRs, issues, commits, diffs, pscode change files). Reference them by path or URL instead.
16
+
17
+ Redact any sensitive information, such as API keys, passwords, or personally identifiable information.
18
+
19
+ If the user passed arguments, treat them as a description of what the next session will focus on and tailor the doc accordingly.
20
+ `;
21
+ }
22
+ export function getHandoffCommandTemplate() {
23
+ return {
24
+ name: 'PS: Handoff',
25
+ description: 'Compact the current conversation into a handoff document for another agent to pick up',
26
+ category: 'Workflow',
27
+ tags: ['handoff', 'sessao', 'continuacao', 'workflow'],
28
+ content: getHandoffInstructions(),
29
+ };
30
+ }
31
+ //# sourceMappingURL=handoff.js.map
@@ -65,7 +65,6 @@ After completing the steps, summarize:
65
65
  - If the name is invalid (not kebab-case), ask for a valid name
66
66
  - If a change with that name already exists, suggest continuing that change instead
67
67
  - Pass --schema if using a non-default workflow`,
68
- license: 'MIT',
69
68
  compatibility: 'Requires pscode CLI.',
70
69
  metadata: { author: 'pscode', version: '1.0' },
71
70
  };
@@ -3,7 +3,6 @@ export function getOnboardSkillTemplate() {
3
3
  name: 'pscode-onboard',
4
4
  description: 'Guided onboarding for Pscode - walk through a complete workflow cycle with narration and real codebase work.',
5
5
  instructions: getOnboardInstructions(),
6
- license: 'MIT',
7
6
  compatibility: 'Requires pscode CLI.',
8
7
  metadata: { author: 'pscode', version: '1.0' },
9
8
  };
@@ -1,9 +1,9 @@
1
+ import { buildNextStepComment } from './trello-next-step-comment.js';
1
2
  export function getProposeSkillTemplate() {
2
3
  return {
3
4
  name: 'pscode-propose',
4
5
  description: 'Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.',
5
6
  instructions: getProposeInstructions(),
6
- license: 'MIT',
7
7
  compatibility: 'Requires pscode CLI.',
8
8
  metadata: { author: 'pscode', version: '1.0' },
9
9
  };
@@ -18,6 +18,13 @@ export function getPsProposeCommandTemplate() {
18
18
  };
19
19
  }
20
20
  function getProposeInstructions() {
21
+ // Pre-filled next-step comment (card title interpolated), shared via the
22
+ // trello-next-step-comment utility and indented to sit inside the Trello
23
+ // comment `text: |` blocks below.
24
+ const applyNextStep = buildNextStepComment('<card title>', '/ps:apply')
25
+ .split('\n')
26
+ .map((line) => (line.length > 0 ? ` ${line}` : ''))
27
+ .join('\n');
21
28
  return `Propose a new change - create the change and generate all artifacts in one step.
22
29
 
23
30
  I'll create a change with artifacts:
@@ -25,7 +32,7 @@ I'll create a change with artifacts:
25
32
  - design.md (how)
26
33
  - tasks.md (implementation steps)
27
34
 
28
- After artifacts are created, a **refinement validation loop** runs: the user reviews the plan, gives feedback, and when satisfied the Trello card is moved to Ready to Dev.
35
+ After artifacts are created, a **refinement validation loop** runs: the Trello card is updated with the refined plan, the user reviews it, gives feedback, and when satisfied the card is moved to Ready to Dev.
29
36
 
30
37
  When ready to implement, run /ps:apply
31
38
 
@@ -203,22 +210,10 @@ Para iniciar a implementação quando aprovado:
203
210
 
204
211
  ---
205
212
 
206
- ### Step R2Ask for user approval
207
-
208
- Use **AskUserQuestion** to ask:
209
-
210
- > "A implementação e o planejamento estão de acordo com o esperado?"
213
+ ### Step R1bUpdate Trello card (before asking for approval)
211
214
 
212
- Options:
213
- - Sim, está refinada mover para Ready to Dev
214
- - 🔄 Não, quero ajustar o plano
215
- - ❌ Cancelar (manter em refinamento)
216
-
217
- **Do NOT update the Trello card description or add any comment before the user approves.**
218
-
219
- ---
220
-
221
- ### Step R2a — If APPROVED (Sim, está refinada)
215
+ So the user can use the Trello card itself as a visual reference when deciding,
216
+ update the card with the refinement content **before** asking for approval.
222
217
 
223
218
  1. **Update Trello card description** (if \`cardId\` exists):
224
219
  Build the description from the artifacts already read in Step R1:
@@ -237,7 +232,7 @@ Options:
237
232
  **Artefatos:** pscode/changes/<name>/
238
233
  \`\`\`
239
234
 
240
- 2. **Add a comment** in Portuguese (if \`cardId\` exists):
235
+ 2. **Add a refinement comment** in Portuguese (if \`cardId\` exists):
241
236
  \`\`\`tool
242
237
  mcp__claude_ai_Trello_Custom__add_comment
243
238
  card_id: "<cardId>"
@@ -250,22 +245,44 @@ Options:
250
245
  ### Resumo
251
246
  <2-3 line summary of what will be built>
252
247
 
253
- ### Para iniciar a implementação
254
- \`\`\`
255
- /ps:apply <name>
256
- \`\`\`
248
+ ${applyNextStep}
257
249
 
258
250
  _Aguardando aprovação para mover para Ready to Dev._
259
251
  \`\`\`
260
252
 
261
- 3. **Move the Trello card to the ready list** (if \`lists.ready\` is configured and \`cardId\` exists):
253
+ If any Trello call fails, continue Trello is auxiliary, never blocking.
254
+
255
+ ---
256
+
257
+ ### Step R2 — Ask for user approval
258
+
259
+ Use **AskUserQuestion** to ask:
260
+
261
+ > "A implementação e o planejamento estão de acordo com o esperado?"
262
+
263
+ Options:
264
+ - ✅ Sim, está refinada — mover para Ready to Dev
265
+ - 🔄 Não, quero ajustar o plano
266
+ - ❌ Cancelar (manter em refinamento)
267
+
268
+ At this point the Trello card already reflects the current refinement (Step R1b),
269
+ so the user can review it before deciding.
270
+
271
+ ---
272
+
273
+ ### Step R2a — If APPROVED (Sim, está refinada)
274
+
275
+ The card description and refinement comment were already added in Step R1b.
276
+ Now just move the card and register the explicit approval.
277
+
278
+ 1. **Move the Trello card to the ready list** (if \`lists.ready\` is configured and \`cardId\` exists):
262
279
  \`\`\`tool
263
280
  mcp__claude_ai_Trello_Custom__update_card
264
281
  card_id: "<cardId>"
265
282
  list_id: "<lists.ready.id>"
266
283
  \`\`\`
267
284
 
268
- 4. **Add a final Trello comment** (if cardId exists):
285
+ 2. **Add a final Trello comment** (if cardId exists):
269
286
  \`\`\`tool
270
287
  mcp__claude_ai_Trello_Custom__add_comment
271
288
  card_id: "<cardId>"
@@ -274,13 +291,10 @@ Options:
274
291
 
275
292
  O planejamento foi revisado e aprovado.
276
293
 
277
- ### Próximo passo
278
- \`\`\`
279
- /ps:apply <name>
280
- \`\`\`
294
+ ${applyNextStep}
281
295
  \`\`\`
282
296
 
283
- 5. **Show success message:**
297
+ 3. **Show success message:**
284
298
  \`\`\`markdown
285
299
  ## ✅ Pronto para desenvolvimento!
286
300
 
@@ -305,9 +319,9 @@ Options:
305
319
  - Changes to technical approach → update \`design.md\`
306
320
  - Changes to tasks → update \`tasks.md\`
307
321
 
308
- 3. **Go back to Step R1** and show the updated refinement summary.
322
+ 3. **Go back to Step R1** and show the updated refinement summary, then **re-run Step R1b**
323
+ so the Trello card description and comment reflect the adjusted plan before asking again.
309
324
  Keep looping until the user approves or cancels.
310
- **Do NOT update the Trello description or add comments until the user approves.**
311
325
 
312
326
  ---
313
327
 
@@ -1,9 +1,9 @@
1
+ import { getNextStepCommentInstructionBlock } from './trello-next-step-comment.js';
1
2
  export function getTrelloDraftSkillTemplate() {
2
3
  return {
3
4
  name: 'pscode-trello-draft',
4
5
  description: 'Capture a raw idea or concept into the Backlog Trello list. Use when the user wants to quickly record something without refining it into a task yet.',
5
6
  instructions: getTrelloDraftInstructions(),
6
- license: 'MIT',
7
7
  compatibility: 'Requires pscode CLI and Trello MCP server configured via /ps:trello-setup.',
8
8
  metadata: { author: 'pscode', version: '1.0' },
9
9
  };
@@ -141,22 +141,10 @@ Save the returned card \`id\` as \`cardId\` and \`url\` as \`cardUrl\`.
141
141
  ## Step 7 — Add next-step comment
142
142
 
143
143
  Add a comment to the card with the command to take this task to the next stage,
144
- formatted in Markdown so it is easy to copy and paste.
144
+ with the card title (\`<title>\` from Step 3) pre-filled as the quoted argument so
145
+ it is ready to copy and paste.
145
146
 
146
- \`\`\`tool
147
- mcp__claude_ai_Trello_Custom__add_comment
148
- card_id: "<cardId>"
149
- text: |
150
- ## Próximo passo
151
-
152
- Para refinar e gerar os artefatos da change, rode:
153
-
154
- \`\`\`
155
- /ps:propose
156
- \`\`\`
157
- \`\`\`
158
-
159
- If this call fails, log the error and continue — the comment is auxiliary, never blocking.
147
+ ${getNextStepCommentInstructionBlock('<title>', '/ps:propose')}
160
148
 
161
149
  ---
162
150
 
@@ -1,26 +1,33 @@
1
1
  /**
2
2
  * Trello Next-Step Comment Utility
3
3
  *
4
- * Builds and posts a Trello comment at the end of each workflow step,
5
- * showing the exact command (with card title pre-filled) to advance
6
- * to the next stage.
4
+ * Builds the Trello comment posted at the end of each workflow step, showing
5
+ * the exact command (with the card title pre-filled as a quoted argument) to
6
+ * advance to the next stage — so the dev can copy/paste without typing the name.
7
7
  *
8
- * Usage: import buildNextStepComment and call it with the current stage
9
- * and card title. Post the result via mcp__claude_ai_Trello_Custom__add_comment.
8
+ * Pure functions, no side effects and no Trello API calls: the card title is
9
+ * already available in each skill's flow. Shared across the ps:draft, ps:propose
10
+ * and ps:apply skill templates to avoid drift in the comment format.
10
11
  */
11
- export type WorkflowStage = 'draft' | 'explore' | 'propose' | 'apply';
12
12
  /**
13
- * Returns the markdown text for the next-step comment.
14
- * Paste this into mcp__claude_ai_Trello_Custom__add_comment as `text`.
13
+ * Returns the Markdown text for the next-step comment: a header, a description
14
+ * line, and a code block with the command and pre-filled, quoted card title.
15
+ * Paste the result into mcp__claude_ai_Trello_Custom__add_comment as `text`.
16
+ *
17
+ * @param cardName The card title to interpolate as the argument.
18
+ * @param nextCommand The command to advance the workflow (e.g. "/ps:apply").
19
+ * @param fallbackChangeName Optional kebab-case change name used when `cardName`
20
+ * is null, undefined or empty.
15
21
  */
16
- export declare function buildNextStepComment(stage: WorkflowStage, cardTitle: string): string;
22
+ export declare function buildNextStepComment(cardName: string, nextCommand: string, fallbackChangeName?: string): string;
17
23
  /**
18
- * Returns the instruction block to be embedded inside a workflow template.
19
- * Tells the AI agent how and when to post the next-step comment.
24
+ * Returns the instruction block embedded inside a workflow skill template. It
25
+ * tells the AI agent to post a next-step comment built via `buildNextStepComment`,
26
+ * with the card title pre-filled as the command argument.
20
27
  *
21
- * @param stage The current workflow stage (determines which command appears)
22
- * @param titleVar The template variable name that holds the card title at runtime
23
- * (e.g. "<title>", "<cardTitle>"). Default: "<title>"
28
+ * @param cardName Placeholder (or literal) card title shown in the generated
29
+ * command (e.g. "<título do card>").
30
+ * @param nextCommand The command to advance the workflow (e.g. "/ps:apply").
24
31
  */
25
- export declare function getNextStepCommentInstructionBlock(stage: WorkflowStage, titleVar?: string): string;
32
+ export declare function getNextStepCommentInstructionBlock(cardName: string, nextCommand: string): string;
26
33
  //# sourceMappingURL=trello-next-step-comment.d.ts.map
@@ -1,56 +1,94 @@
1
1
  /**
2
2
  * Trello Next-Step Comment Utility
3
3
  *
4
- * Builds and posts a Trello comment at the end of each workflow step,
5
- * showing the exact command (with card title pre-filled) to advance
6
- * to the next stage.
4
+ * Builds the Trello comment posted at the end of each workflow step, showing
5
+ * the exact command (with the card title pre-filled as a quoted argument) to
6
+ * advance to the next stage — so the dev can copy/paste without typing the name.
7
7
  *
8
- * Usage: import buildNextStepComment and call it with the current stage
9
- * and card title. Post the result via mcp__claude_ai_Trello_Custom__add_comment.
8
+ * Pure functions, no side effects and no Trello API calls: the card title is
9
+ * already available in each skill's flow. Shared across the ps:draft, ps:propose
10
+ * and ps:apply skill templates to avoid drift in the comment format.
10
11
  */
11
- const NEXT_STEP = {
12
- draft: { command: 'ps:propose', label: 'Refinar e gerar os artefatos da change' },
13
- explore: { command: 'ps:propose', label: 'Propor a change com todos os artefatos' },
14
- propose: { command: 'ps:apply', label: 'Implementar as tasks da change' },
15
- apply: { command: 'ps:complete', label: 'Finalizar e sincronizar a change' },
12
+ /** Human-readable label describing what the next command does. */
13
+ const NEXT_STEP_LABELS = {
14
+ '/ps:propose': 'Para refinar e gerar os artefatos da change',
15
+ '/ps:apply': 'Para implementar as tasks da change',
16
+ '/ps:complete': 'Para finalizar e arquivar a change',
16
17
  };
18
+ /** Normalizes a command so it always starts with a single leading slash. */
19
+ function normalizeCommand(nextCommand) {
20
+ const trimmed = (nextCommand ?? '').trim();
21
+ if (trimmed.length === 0)
22
+ return '';
23
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
24
+ }
25
+ /** Converts an arbitrary string into a kebab-case identifier. */
26
+ function toKebabCase(value) {
27
+ return (value ?? '')
28
+ .normalize('NFD')
29
+ .replace(/[̀-ͯ]/g, '')
30
+ .trim()
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9]+/g, '-')
33
+ .replace(/^-+|-+$/g, '');
34
+ }
35
+ /**
36
+ * Resolves the argument placed after the command. Uses the card title when
37
+ * available, otherwise falls back to the kebab-case change identifier. Internal
38
+ * double quotes are escaped so the generated command stays valid.
39
+ */
40
+ function resolveArgument(cardName, fallbackChangeName) {
41
+ const name = (cardName ?? '').trim();
42
+ const resolved = name.length > 0 ? name : toKebabCase(fallbackChangeName) || 'nova-change';
43
+ return resolved.replace(/"/g, '\\"');
44
+ }
17
45
  /**
18
- * Returns the markdown text for the next-step comment.
19
- * Paste this into mcp__claude_ai_Trello_Custom__add_comment as `text`.
46
+ * Returns the Markdown text for the next-step comment: a header, a description
47
+ * line, and a code block with the command and pre-filled, quoted card title.
48
+ * Paste the result into mcp__claude_ai_Trello_Custom__add_comment as `text`.
49
+ *
50
+ * @param cardName The card title to interpolate as the argument.
51
+ * @param nextCommand The command to advance the workflow (e.g. "/ps:apply").
52
+ * @param fallbackChangeName Optional kebab-case change name used when `cardName`
53
+ * is null, undefined or empty.
20
54
  */
21
- export function buildNextStepComment(stage, cardTitle) {
22
- const next = NEXT_STEP[stage];
23
- const escapedTitle = cardTitle.replace(/"/g, '\\"');
24
- return `**Avançar etapa:** ${next.label}
55
+ export function buildNextStepComment(cardName, nextCommand, fallbackChangeName = '') {
56
+ const command = normalizeCommand(nextCommand);
57
+ const label = NEXT_STEP_LABELS[command] ?? 'Para avançar para a próxima etapa';
58
+ const argument = resolveArgument(cardName, fallbackChangeName);
59
+ return `## Próximo passo
60
+
61
+ ${label}, rode:
25
62
 
26
63
  \`\`\`
27
- /${next.command} "${escapedTitle}"
64
+ ${command} "${argument}"
28
65
  \`\`\``;
29
66
  }
30
67
  /**
31
- * Returns the instruction block to be embedded inside a workflow template.
32
- * Tells the AI agent how and when to post the next-step comment.
68
+ * Returns the instruction block embedded inside a workflow skill template. It
69
+ * tells the AI agent to post a next-step comment built via `buildNextStepComment`,
70
+ * with the card title pre-filled as the command argument.
33
71
  *
34
- * @param stage The current workflow stage (determines which command appears)
35
- * @param titleVar The template variable name that holds the card title at runtime
36
- * (e.g. "<title>", "<cardTitle>"). Default: "<title>"
72
+ * @param cardName Placeholder (or literal) card title shown in the generated
73
+ * command (e.g. "<título do card>").
74
+ * @param nextCommand The command to advance the workflow (e.g. "/ps:apply").
37
75
  */
38
- export function getNextStepCommentInstructionBlock(stage, titleVar = '<title>') {
39
- const next = NEXT_STEP[stage];
40
- const escapedTitleVar = titleVar.replace(/"/g, '\\"');
76
+ export function getNextStepCommentInstructionBlock(cardName, nextCommand) {
77
+ const comment = buildNextStepComment(cardName, nextCommand);
78
+ const indented = comment
79
+ .split('\n')
80
+ .map((line) => (line.length > 0 ? ` ${line}` : ''))
81
+ .join('\n');
41
82
  return `## Step — Add next-step comment
42
83
 
43
- Post a comment on the card so the team always has the ready-to-paste command for the next stage.
84
+ Post a comment on the card with the ready-to-paste command for the next stage,
85
+ using \`buildNextStepComment\` so the card title is pre-filled as the quoted argument:
44
86
 
45
87
  \`\`\`tool
46
88
  mcp__claude_ai_Trello_Custom__add_comment
47
89
  card_id: "<cardId>"
48
90
  text: |
49
- **Avançar etapa:** ${next.label}
50
-
51
- \`\`\`
52
- /${next.command} "${escapedTitleVar}"
53
- \`\`\`
91
+ ${indented}
54
92
  \`\`\`
55
93
 
56
94
  If this call fails, log the error and continue — the comment is auxiliary, never blocking.`;
@@ -3,7 +3,6 @@ export function getTrelloSetupSkillTemplate() {
3
3
  name: 'pscode-trello-setup',
4
4
  description: 'Configure Trello integration for your Pscode workflow. Checks MCP availability, reads or creates a Trello board, and writes pscode/trello.yaml with your stage-to-list mapping.',
5
5
  instructions: getTrelloSetupInstructions(),
6
- license: 'MIT',
7
6
  compatibility: 'Requires pscode CLI and the Trello MCP server.',
8
7
  metadata: { author: 'pscode', version: '1.0' },
9
8
  };
@@ -162,7 +162,6 @@ Use clear markdown with:
162
162
  - Code references in format: \`file.ts:123\`
163
163
  - Specific, actionable recommendations
164
164
  - No vague suggestions like "consider reviewing"`,
165
- license: 'MIT',
166
165
  compatibility: 'Requires pscode CLI.',
167
166
  metadata: { author: 'pscode', version: '1.0' },
168
167
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thiagodiogo/pscode",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "pscode",